匿名方法
与前面的可空类型是一样的,匿名方法也是C# 2.0里面提出来的。
1 匿名方法
1.1 什么是匿名方法?
顾名思义,就是没有名称的方法,因为没有名称,匿名方法只能在函数定义(匿名方法是把方法的实现和定义嵌套在了一起)的时候被调用,在其他任何情况下都不能被调用。对于编译器来说,匿名方法并不是没有名字的,编译器在编译匿名方法时会为其生成一个方法名,下面可以通过IL代码来证明。
1 namespace 匿名方法 2 { 3 class Program 4 { 5 private delegate void VoteDelegate(string name); 6 static void Main(string[] args) 7 { 8 //使用匿名方法来实例化委托对象 9 VoteDelegate voteDelegate = delegate(string name) 10 { 11 Console.WriteLine("昵称为:{0} 来帮我提高啦",name); 12 }; 13 //通过调用委托来回调Vote方法,这是隐式调用方式 14 voteDelegate("Hong"); 15 Console.ReadKey(); 16 } 17 } 18 }
从以上的代码可以看出,委托对象可用匿名方法来实例化的。当并不能一概而论,匿名方法也有它本身的缺点——不能在其他地方被调用,即不具有复用性。而且,匿名方法会自动形成“闭包”。当一个函数(这里成为外部函数)包含对另一个函数(内部函数)的调用时,或当内部函数使用了外部函数的变量时,都会形成闭包。闭包可能会延长外部变量的生命周期。所以如果委托包装的方法相对简单,并且该方法在其他地方的调用频率较低,你就可以考虑用匿名方法来实例化委托对象。
1.2 对变量捕捉过程的剖析
变量被匿名方法捕获后,变量的生命周期会被延长,就是说对于一个被捕获的变量而言,只要还有任何委托实例在引用它,它就一直存在,就不会在部分委托实例调用结束后被垃圾回收释放掉。下面通过代码来解释:
1 class Program 2 { 3 private delegate void ClosureDelegate(); 4 static void Main(string[] args) 5 { 6 ClosureDelegate test = CreateDelegateInstance(); 7 test(); 8 Console.ReadKey(); 9 } 10 11 private static ClosureDelegate CreateDelegateInstance() 12 { 13 //外部变量 14 int count = 1; 15 ClosureDelegate closureDelegate = delegate() 16 { 17 Console.WriteLine(count); 18 count++; 19 }; 20 closureDelegate(); 21 return closureDelegate; 22 } 23 }
结果:
我并不知道大家对这个结果是否感到惊讶,第一次输出1是正常的,但第二个输出2确实比较奇妙的。大家可能会认为应该抛出异常才对,因为count在栈上的生命周期已经结束。但结果确实是输出2,是不会错的,我们可以倒推原因,使用匿名方法时,编译器会创建一个额外的类来容纳变量,此时count变量会被分配到堆上的。CreateDelegateInstance方法有该类的一个实例引用,所以此时匿名方法捕捉到的是变量count的一个引用,而不是真真的值。在这个过程中,匿名方法延长了变量count的生命周期。
对于匿名方法捕捉到的变量,编译器会额外创建一个类来容纳它们。
由上图可知,我们并没有在源码中定义<>c_DisplayClass2_0类。这个类就是编译器创建来容纳捕获的变量count的,该类还容纳了CreateDelegateInstance的方法。