📜 ⬆️ ⬇️

Delegates and Lambda Expressions in C # .Net - Cheat Sheet or Briefly

Hello, dear reader!


Almost everyone who has worked in .Net more or less knows what Delegates are. And those who do not know about them, almost certainly at least in the know about Lambda expressions (Lambda expressions). But personally, I constantly forget about the syntax of their declarations, then come back to the multipage explanations of smart people about how the compiler reacts to such constructions. If such a problem happens to you, you are welcome!

Delegates


A delegate is a special type. And it is announced in a special way:

delegate int MyDelegate (string x); 

Everything is simple, there is the delegate keyword, and then the delegate itself with the name MyDelegate, returned by the int type and one argument of the string type.

In fact, when compiling code in a CIL, the compiler turns each such delegate type into the same type-class and all instances of a given delegate type are in fact instances of the corresponding class-types. Each such class inherits the type MulticastDelegate from which it gets the Combine and Remove methods, contains a constructor with two arguments target (Object) and methodPtr (IntPtr), a field invocationList (Object), and three of its own methods Invoke, BeginInvoke, EndEnvoke .
')
By declaring a new delegate type, we immediately, through the syntax of its declaration, strictly define the signature of valid methods with which instances of such a delegate can be initialized. This immediately affects the signature of the auto-generated methods Invoke, BeginInvoke, EndEnvoke, so these methods are not inherited from the base type, but are defined for each delegate type separately.

An instance of such a delegate should be understood as a link to a specific method or list of methods that will be transmitted somewhere and most likely executed on the other side. Moreover, the client will not be able to pass with the method the value of the arguments with which it will be executed (if only we do not allow it to it), or change its signature. But he can determine the logic of the method, that is, his body.

This is convenient and safe for our code, since we know what type of argument to pass to the delegate at runtime and what return type to expect from the delegate.

If we dream up, then we can provide the right to pass an argument for delegates to the client side, for example, to create a method with a delegate argument and an argument that will be passed to this delegate within our method, which will allow the client to specify the argument value for the method in the delegate. For example in this way.

 void MyFunc(myDelegate deleg, int arg){deleg.Invoke(arg);} 

By creating an instance of the delegate in the code, a method is passed to its constructor (both the instance and static approaches, the main thing is that the method signature matches the delegate signature). If the method is instance-specific, then in the target field a reference is written to the instance owner of the method (we need it, because if the method is instance- specific, this at least implies working with the fields of this target object), and in methodPtr, a reference to the method. If the method is static, then the target and fieldPtr fields are written null and the method reference, respectively.

You can initialize a delegate variable through creating an instance of the delegate:

 MyDeleg x = new MyDeleg(MyFunc); 

Or simplified syntax without calling a constructor:

 MyDeleg x = MyFunc; 

You can organize the transfer / receipt of a delegate instance in different ways. Since the delegate as a result is just a type-class, you can freely create fields, properties, method arguments, etc. specific type of delegate.

Delegate methods:

Invoke - synchronous execution of the method that is stored in the delegate.
BeginInvoke, EndEnvoke - is similar but asynchronous.

You can also call the execution of methods stored in the delegate via the simplified syntax:

 delegInst.Invoke(argument); 

it's like writing:

 delegInst(argument); 

And what for to the delegate a field invocationList?


The invocationList field is null for the delegate instance while the delegate stores a reference to one method. This method can always be rewritten to another by equating the variable with a new argument to the delegate (or the method we need right away through a simplified syntax). But you can also create a call chain when the delegate stores references to more than one method.
To do this, call the Combine method:

 MyDeleg first = MyFunc1; MyDeleg second = MyFunc2; first = (MyDeleg) Delegate.Combine(first, second); 

The Combine method returns a reference to a new delegate in which the target and methodPtr fields are empty, but invocationList, which contains two references to delegates: the one that was previously in the first variable and the one that is still stored in second. It is necessary to understand that adding the third delegate through the Combine method and writing its result to first, the method returns a reference to the new delegate with an invocationList field in which there will be a collection of three references, and the delegate with two references will be deleted by the garbage collector during the next cleaning cycle.

When such a delegate is executed, all its methods will be executed in turn. If the delegate signature assumes receiving parameters, the parameters for all methods will have the same value. If there is a return value, then we can only get the value of the last method in the list.

The Remove method, in turn, performs a search in the list of delegates by the value of the owner object and method, and, if found, deletes the first matched.

 Deleg first = first.Remove(MyFunc2); 

The + = and - = operators defined for delegates are analogous to the Combine and Remove methods:

 first = (Deleg) Delegate.Combine(first, second); 

similar to the following entry:

 first += MyFunc2; 

And correspondingly:

 first = first.Remove(MyFunc2); 

similar to the following entry:

 first -= MyFunc; 

It is worth saying that delegates can be generic (Generic), which is a more correct approach to creating separate delegates for different types.

It is also worth mentioning that the FCL library already contains the most popular delegate types (generic and none) . For example, the delegate Action <T> is a method with no return value but with an argument, and Fucn <T, TResult> with a return value and an argument.

Lambda Operators and Lambda Expressions


You can also initialize the delegate instance with a lambda operator (lambda-operator) or a lambda expression (lambda-expression). Since, on the whole, they are the same, then hereinafter I will simply call them “lambdas” in places where there is no need to emphasize their differences.
It is worth mentioning that they were introduced in C # 3.0, and before them there were anonymous functions that appeared in C # 2.0.

A distinctive feature of lambda is the operator =>, which divides the expression into the left part with the parameters and the right part with the method body .

Suppose we have a delegate:

 delegate string MyDeleg (string verb); 

Then the general syntax of the lambda operator will be as follows:

 MyDeleg myDeleg = (string x) => { return x; }; 

This is exactly the Lambda operator since we frame its body in braces, which allows us to place more than one operator in it:

 MyDeleg myDeleg = (string x) => { var z = x + x; return z; }; 

It is allowed not to specify the types of arguments, because the compiler already knows the type and signature of your delegate, but you can also specify for ease of reading the code by another person:

 MyDeleg myDeleg = (x) => { return x; }; 

If there is only one argument, you can omit the brackets enclosing it:

 MyDeleg myDeleg = x => { return x; }; 

If there is no argument in the delegate signature, then you must specify empty brackets:

 AnotherDeleg myDeleg = () => { return x; }; 


If a lambda body consists of only one expression, then it is a lambda expression . This is very convenient, since we have the opportunity to use a simplified syntax in which:

- you can omit the curly brackets framing the body of the lambda;

- without the above-mentioned curly brackets, we do not need to use the return keyword before the operator and the comma after the operator in the body of the lambda:

As a result, the lambda definition code may become tiny:
 MyDeleg myDeleg = x => x+x; 


What does the compiler think about lambdas?


It is important to understand that lambda expressions are not magic strings transmitted directly to the delegate. In fact, at the compilation stage, each such expression is turned into an anonymous private method with the name beginning with "<", which excludes the possibility of calling such a method directly. This method is always a member of the type in which you use the given lambda expression , and is passed to the delegate constructor explicitly in CIL code.

Moreover, the compiler analyzes whether the expression contains in its body operations with instance fields of the type in which the expression is updated or not. If so, the generated method will be an instance method, and if not, the method will be static. The use of static fields of a given type in lambda expressions, as well as instances and instance fields of other types, do not affect this.

You may ask why the CLR does not generate an instance method in both cases, the answer is simple - this method needs the extra parameter this, which makes it more difficult to execute compared to a static one.

In addition, the CLR creates a construct that caches the delegate with our method in an anonymous private field (all the same in our type where the lambda expression was used) on the first call to it, and on subsequent ones it just reads from the field. Indeed, there is no point in creating it anew each time, because the information about the method given by the expression is unchanged at the program execution stage.

Eventually


But in the end I spent the whole evening ... Phew! I tried to make the cheat sheet the most compact and informative, but still somehow a lot of letters came out. Thanks for the comments in advance, I will try to immediately correct all my flaws.

Special thanks to the great Jeffrey Richter, who of course will not read the article, but he just wrote a wonderful book “CLR via C #”, which I reread again and again, and the information from which I used when writing this cheat sheet.

Thank you all very much!

Source: https://habr.com/ru/post/329886/


All Articles