只有接口和委托才具有协变和逆变
在非泛型方法中,协变和逆变无法讨论,因为完全按照自然规则去运行,父类->子类,强制转换。子类->父类,直接转换。
委托参数的逆变性
逆变就是将父类变成子类
逆变用在返回类型上
委托返回类型的协变性
协变就是将子类变成父类
协变用在参数上
使用匿名方法的内联委托操作
匿名方法在编译时实际上是创建了一个内部方法
如果不需要参数,则可以完全忽略掉参数,直接使用delegate{cw...}
匿名方法中捕获变量
闭包:一个函数除了能通过提供给它的参数交互之外,还能同环境进行更大的交互
外部变量:作用域内包括匿名方法的局部变量或参数(不包括ref和out参数)。在类的实例成员内部的匿名方法中,this引用也被认为是一个外部变量
捕获的外部变量:在匿名方法内部的外部变量
void EnclosingMethod()
{
//外部变量
int outerVariable = 5;
//外部变量
string capturedVariable = "captured";
if (DateTime.Now.Hour == 23)
{
//普通方法的局部变量
int normalLocalVariable = DateTime.Now.Minute;
Console.WriteLine(normalLocalVariable);
}
MethodInvoker x = delegate()
{
//匿名方法的局部变量
string anonLocal = "local to anonymous method";
//捕获外部变量
Console.WriteLine(capturedVariable + anonLocal);
};
x();
}
捕获变量的行为
被匿名方法捕捉到的确实是变量,而不是创建委托实例时该变量的值
Where(a=>a>limit).ToList()中的limit就是一个捕获变量,也就是where内部不需要固定死limit的的值,完全由外部决定
匿名方法中捕获的外部变量不存储在栈上,编译器创建了一个额外的类来容纳外部变量(不同的编译器处理方式可能不同)。或者说局部变量并非始终是局部的,即时在方法返回之后,它依然存在。
委托中包含的内部变量会在创建一个新的委托时新建个内部变量的实例,在调用委托时,会使用各自的那个实例,当同样一个委托多次调用时,就不会生成新的变量实例了,而是用原来的那个实例,而这个实例的所有变化都会影响到后面的变化。
共享和非共享的变量混合使用
static void Main(string[] args)
{
MethodInvoker[] methodInvokers = new MethodInvoker[2];
int outside = 0;
for (int i = 0; i < 2; i++)
{
int inside = 0;
methodInvokers[i] = delegate
{
Console.WriteLine($"{outside},{inside}");
outside++;
inside++;
};
}
MethodInvoker first = methodInvokers[0];
MethodInvoker second = methodInvokers[1];
first();
first();
first();
second();
second();
second();
Console.ReadLine();
}
//0,0
//1,1
//2,2
//4,0
//5,1
//6,2
编译器为我们生成了两个额外的变量
捕获变量的规则小结
1.如果用或不用捕获变量时的代码同样简单,那就不用
2.捕获由for或foreach语句声明的变量之前,思考你的委托是否需要在循环迭代结束之后延续,以及是否想让它看到那个变量的后续,如果不是,就在循环内另建一个变量
3.如果创建多个委托实例(不管是在循环内,还是显示的创建),而且捕获了变量,思考一下是否希望他们捕获同一个变量
4.如果捕获的变量不会发生改变(不管是在匿名方法中,还是在保卫这匿名方法的外层方法主体重),就不需要担心
5.如果创建的委托实例永远不从方法中逃脱,或者说,它们永远不会存储到别的地方,不会反悔,也不会用于启动线程,就会变简单很多
6.从垃圾回收的角度,思考任何捕获变量被延长的生存期。一般问题不大,但是捕获的对象会产生昂贵的内存开销,问题就会凸显。