Delegate
Preface: C#1 already has the concept of entrust, but its complicated usage does not arouse much attention of the developer, in the c#2, some compiler optimizations are made, and an anonymous method can be used to create a delegate. At the same time, the conversion of method groups and delegates is also supported. By the way, the covariance and contravariance of delegates are increased in the c#2.
Method Group Conversions
The meaning of a method group is derived from the overloading of methods: we can define a bunch of methods whose names are the same, but accept different arguments or return types (in short, different signatures----except for names), which is the overloaded method.
public static void SomeMethod (object helloworld)
{
Console.WriteLine (helloworld);
}
public static void SomeMethod ()
{
Console.WriteLine ("hello world");
}
ThreadStart ts = SomeMethod;
ParameterizedThreadStart ps = SomeMethod;
The two calls shown above are no problem, the compiler can find a corresponding method to instantiate the corresponding delegate, but the problem is that the Thread class itself has been overloaded to use ThreadStart and ParameterizedThreadStart (here is an example , Of course, for all such cases), passing in a method group will cause the compiler to report an error:
Thread t = new Thread (SomeMethod); // The compiler reports an error: the method call is ambiguous
The same situation cannot be used to directly convert a method group to Delegate, and it needs to be explicitly converted:
Delegate parameterizedThreadStart = (ParameterizedThreadStart) SomeMethod;
Delegate threadStart = (ThreadStart) SomeMethod;
Covariance and contravariance
C # 1 does not support the covariance and contravariance of delegates, which means that a method must be defined for each delegate to match. C # 2 supports delegated covariance and contravariance, which means we can write the following code:
Assume two classes, one of which inherits the other:
public class BaseClass {}
public class DerivedClass: BaseClass {}
C # 2 supports the following:
class Program
{
delegate BaseClass FirstMethod (DerivedClass derivedClass);
static void Main (string [] args)
{
FirstMethod firstMethod = SomeMethod;
Console.ReadKey ();
}
static DerivedClass SomeMethod (BaseClass derivedClass)
{
return new DerivedClass ();
}
}
In C # 4, covariance and contravariance of generic types and generic delegates are supported:
public class BaseClass {}
public class DerivedClass: BaseClass {}
Func <BaseClass, DerivedClass> firstFunc = delegate (BaseClass baseClass)
{
return new DerivedClass ();
};
Func <DerivedClass, BaseClass> secondFunc = firstFunc;
Essentially covariance and contravariance on C # 4 generics are just conversions between references, without creating a new object later.
Risk of incompatibility
C # 2 supports the following problems after commissioning covariance and inverter:
Suppose now that BaseClass and DerivedClass are changed to the following:
public class BaseClass
{
public void CandidateAction (string x)
{
Console.WriteLine ("Baseclass.CandidateAction");
}
}
public class DerivedClass: BaseClass
{
public void CandidateAction (object x)
{
Console.WriteLine ("Derived.CandidateAction");
}
}
DerivedClass overloads the methods in BaseClass. Due to the generic inversion and covariance of C # 2, write the following code:
class Program
{
delegate void FirstMethod (string x);
static void Main (string [] args)
{
DerivedClass derivedClass = new DerivedClass ();
FirstMethod firstMethod = derivedClass.CandidateAction;
firstMethod ("hello world"); // DerivedClass.CandidateAction
Console.ReadKey ();
}
}
The output is "DerivedClass.CandidateAction! The result you see must be the result in C # 2 and later. If it is in C # 1, then the result should be the output" BaseClass.CandidateAction "
Anonymous method
The anonymous method below is the creator of a series of important concepts such as linq and lambda.
The first problem he has to solve is that the delegate in C # 1 is too tedious to call. In C # 1, to establish a delegate and use this delegate usually go through four steps. The key is to write a method specifically called by the delegate into the class no matter how simple a delegate is to be called. If there is no suitable For classes, you also need to create a new class. . .
Anonymous methods are a little trick of the compiler. The compiler will create a class in the background to contain the method represented by the anonymous method, and then pass through the four parts like a normal delegate call. The CLR doesn't know about anonymous delegation at all, as if it doesn't exist.
If you don't care about the parameters, you can omit: delegate {... do something ..}, but when it comes to method overloading, you should supplement the corresponding parameters according to the compiler's prompt.
Variables captured by anonymous methods
Closure.
delegate void MethodInvoker ();
void EnclosingMethod ()
{
int outerVariable = 5; //? external variables (uncaught variables)
string capturedVariable = "captured"; //? External variables captured by anonymous methods
if (DateTime. Now. Hour == 23)
{
int normalLocalVariable = DateTime. Now. Minute; //? Local variables for normal methods
Console. WriteLine (normalLocalVariable);
}
MethodInvoker x = delegate ()
{
string anonLocal = "local to anonymous method"; //? Local variables for anonymous methods
Console. WriteLine (capturedVariable + anonLocal); //? Capture external variables
};
x ();
}
What is captured by the anonymous method is indeed the variable, not the value of the variable when the delegate instance was created. The value of this captured variable is collected only when the delegate is executed:
int a = 4;
MethodInvoker invoker = delegate ()
{
a = 5;
Console.WriteLine (a);
};
Console.WriteLine (a); // 4
invoker (); // 5
The point is that throughout the method we use the same captured variable.
Benefits of capturing variables
In short, capturing variables can simplify avoiding creating special classes to store the information that a delegate needs to process (in addition to the information passed as parameters).
Life cycle of captured variables
For a captured variable, it will persist as long as there are any delegate instances referencing it.
delegate void MethodInvoker ();
static MethodInvoker CreateMethodInvokerInstance ()
{
int a = 4;
MethodInvoker invoker = delegate ()
{
Console.WriteLine (a);
a ++;
};
invoker ();
return invoker;
}
static void Main (string [] args)
{
MethodInvoker invoker = CreateMethodInvokerInstance (); // 4
invoker (); // 5
invoker (); // 6
Console.ReadKey ();
}
It can be seen that after the execution of CreateDelegateInstance, its corresponding stack frame has been destroyed. It stands to reason that the local variable a will also end with it, but it will continue to output 5 and 6, because the compiler creates the anonymous method. That class captured this variable and saved its value! CreateDelegateInstance has a reference to an instance of the class, so it can use the variable a, and the delegate also has a reference to an instance of the class, so it can also use the variable a. This instance is on the heap just like any other instance.
Local variable instantiation
Whenever execution reaches the scope where a local variable is declared, the local variable is said to be instantiated.
Local variables are declared on the stack, so you don't have to instantiate every loop in a structure like for.
The effect of a local variable being declared multiple times is different from a single declaration.
delegate void MethodInvoker ();
static void Main (string [] args)
{
List <MethodInvoker> methodInvokers = new List <MethodInvoker> ();
for (int i = 0; i <10; i ++)
{
int count = i * 10;
methodInvokers.Add (delegate ()
{
Console.WriteLine (count);
count ++;
});
}
foreach (var item in methodInvokers)
{
item ();
}
methodInvokers [0] (); // 1
methodInvokers [0] (); // 2
methodInvokers [0] (); // 3
methodInvokers [1] (); // 11
Console.ReadKey ();
}
In the above example, count is re-created in each loop, which causes the variables captured by the delegate to be new and different variables, so the values maintained are different.
If you remove count, replace it with this:
delegate void MethodInvoker ();
static void Main (string [] args)
{
List <MethodInvoker> methodInvokers = new List <MethodInvoker> ();
for (int i = 0; i <10; i ++)
{
methodInvokers.Add (delegate ()
{
Console.WriteLine (i);
i ++;
});
}
foreach (var item in methodInvokers)
{
item ();
}
methodInvokers [0] ();
methodInvokers [0] ();
methodInvokers [0] ();
methodInvokers [1] ();
Console.ReadKey ();
}
This delegate directly captures the variable i. The loop variable in the for loop is considered to be a variable declared outside the for loop, similar to the following code:
int i = 0;
for (i; i <10; i ++)
{
.....
}
Note that this example can be convinced by the fact that local variables are instantiated only once or multiple times. The principle behind this is that the class created by the compiler is instantiated differently. The first time the count variable is used to accept the value of i, the compiler will create a new instance to save the count value and be called by the delegate every time inside the for loop. When the count is removed, the compiler creates this The class will be created outside the for loop, so it will only be created once, and the final value of i will be captured. Therefore, I guess that the class created by the compiler is related to the scope of the captured variable. The instantiation position of the class created by the compiler should be the same as the position or instantiation of the captured variable. The domain is the same.
Look at the following example:
delegate void MethodInvoker ();
static void Main (string [] args)
{
MethodInvoker [] methods = new MethodInvoker [2];
int outSide = 1;
for (int i = 0; i <2; i ++)
{
int inside = 1;
methods [i] = delegate ()
{
Console.WriteLine ($ "outside: {outSide} inside: {inside}");
outSide ++;
inside ++;
};
}
MethodInvoker first = methods [0];
MethodInvoker second = methods [1];
first ();
first ();
first ();
second ();
second ();
Console.ReadKey ();
}
This picture illustrates the problem above.
When using capture variables, follow these rules.
If the code is equally simple with or without capturing variables, don't use it.
Before capturing a variable declared by a for or foreach statement, think about whether your delegate needs to continue after the loop iteration ends, and whether you want it to see subsequent values for that variable. If needed, create another variable inside the loop to copy the value you want. (In C # 5, you don't have to worry about the foreach statement, but you still have to be careful with the for statement.) If you create multiple delegate instances (whether in a loop or explicitly) and you capture the variables, think about whether you want them Capture the same variable.
If the captured variables don't change (whether in the anonymous method or in the outer method body surrounding the anonymous method), you don't need to worry so much.
If you create delegate instances that never "escape" from a method, in other words, they will never be stored elsewhere, they won't return, and they won't be used to start threads-then things will be much simpler.
From the perspective of garbage collection, think about the extended lifetime of any capture variable. The problem in this area is generally not large, but if the captured object will cause expensive memory overhead, the problem will be highlighted.
[英] Jon Skeet. Understanding C # (3rd Edition) (Turing Programming Series) (Kindle Location 4363-4375). People's Posts and Telecommunications Press. Kindle Version.
Highlights of this chapter
The variable is captured, not its value when the delegate instance was created.
The lifetime of a captured variable is extended, at least as long as the delegate that captured it.
Multiple delegates can capture the same variable ...
... but inside the loop, the same variable declaration actually references different variable "instances".
Variables created in the declaration of the for loop are only valid for the duration of the loop-they are not instantiated on every loop iteration. This situation also applies to foreach statements before C # 5.
Create additional types if necessary to hold capture variables. be careful! Simplicity is almost always better than being clever.
C # Review Notes (3)-C # 2: Solve the C # 1 problem (delegation to enter the fast track)