We know that in an anonymous method or lambda, you can access or modify the variables within the defined range of the token. For example:
int num = 1; Func<int> incNum = () => ++num;
Lambda expressions use the variable num defined externally. We can think that the lambda statement block in this section constitutes a closure, which captures the external variable num.
Well, let alone so many uncomfortable definitions. Let's start with the question and see how variables are captured in C. Let's look at an example:
Public func <string> createfunction () {string STR = "My lucky number is"; int num = 17; func <string> func = () => STR + num; return func ;}
In this example, a createfunction method is defined to return a function. The returned function forms a closure that captures two variables: Str of the string type and num of the int type.
Now we can use this function as follows:
Func<String> myFunc = CreateFunction(); String result = myFunc();
Let's analyze what the two lines of code actually do. The first line is easy to understand. We assigned the anonymous function generated by createfunction to the delegate myfunc. The second line is better understood. We executed myfunc and assigned the returned result to the result variable. Let's take a deeper look: When executing myfunc, two variables STR and num are defined in createfunction. Although the stack frame of createfunction has been destroyed for a long time, the variables defined in createfunction are still "unknown", but we know that these two variables have been captured by the closure, so we firmly believe that these two variables can still be accessed so far!
For a STR object, given that it is a reference type, it will not be destroyed as long as there is a "thing" that stores references to it. In this way, we do not have to worry that the compiler or runtime will tell us that it is lost when we need it. However, for num, the situation is somewhat different. Num is a value type. We know that the value type is alive on the stack. We also know that the stack frame (I .e. the createfunction frame) exists in it will be destroyed after createfunction is executed, then any value types on it will be destroyed together, which includes the variable num that we are concerned.
So why can we securely access num? What is the magic of the variable capture mechanism in C # that allows the value type to have a life cycle that violates the conventional rules? Packing! You may immediately think that by adding each value type to an object, we can make this value type have the same lifetime as the object wrapped in it. However, this is not the method selected by the C # implementer! C # Does not pack each value type variable to be captured, instead, all the captured variables are put in the same big "box"-when the compiler encounters a situation where variable capture is required, it will silently construct a type in the background, this type contains the variables captured by each closure (including value type variables and reference type variables) as one of its public fields. In this way, the compiler can
Easy and pleasant
Maintain external variables that appear in anonymous functions or lambda expressions.
Furthermore, if we use the ildasm tool to view the Il code of the createfunction method, we will find that the compiler does not declare the num and STR variables at all. Instead, a type name and Instance name are declared and their ugly packaging objects are declared. This is the one we mentioned above, which is silently generated by the compiler and stores all referenced objects that capture variables. We can also see that
Createfunction Method
C # all operations on STR and num in the source code are converted to operations on public members with the same name as the packaging object in Il. By the way, even the lambda expression "() => STR + num" We constructed is now converted into a method for packaging this object by the compiler!