最近看到一本书写到关于匿名方法的使用,写的比较深刻,今天在这里总结一下。由于第一次写博客,如果有不妥的地方,请大家见谅,下面我们转入正题:
1.委托
使用委托工作有两种方式:一种是事先定义好一个方法,然后委托到该方法上,另外一种就是直接在代码中使用匿名方法。
直接使用委托:
public delegate void Print(string printStr); static void Main(string[] args) { Print print = DelegateMethod; print("This is the first DelegateFunction!"); Console.Read(); } public static void DelegateMethod(string printStr) { Console.WriteLine(printStr); }
使用匿名方法:
static void Main(string[] args) { Print print = delegate(string printStr) { Console.WriteLine(printStr + "the first DelegateFunction!"); }; print("This is "); Console.Read(); }
在使用匿名方法时候,要注意不能使用跳转语句跳转到该匿名方法的外部,同样不能用跳转语句从外部跳转到匿名方法内部,匿名方法中不能访问不安全代码(unsafe),也 不能访问在匿名方法外部使用的ref和out参数。
2.匿名方法
匿名方法在上面已经进行了简单的使用,但是在实际问题中可能遇到的问题要比上面的代码复杂得多,在匿名方法中捕获变量就是难点之一。
一个简单的例子(我们将上面的代码进行简单的改变):
public delegate void Print(); static void Main(string[] args) { string temp = "this is "; string localStr = " the first "; Print print = delegate() { Console.WriteLine(temp + localStr + "DelegateFunction!"); }; print(); Console.Read(); }
和你预期的相同,控制台会显示“this is the first DelegateFunction!”。然而我们进行一下改变:
public delegate void Print(); static void Main(string[] args) { string temp = "this is "; string localStr = " the first "; Print print = delegate() { Console.WriteLine(temp + localStr + "DelegateFunction!"); localStr = " the second "; }; localStr = " the third "; print();//打印出 this is the third DelegateFunction! print();//打印出 this is the second DelegateFunction! Console.Read(); }
我们来分析一下结果:首先temp作为匿名方法外部变量,在匿名方法中没有对它进行操作,localStr 在匿名方法中我们捕获它并赋值为“the second” ,然而从结果分析来 看这个赋值的操作显然是在打印出this is the third DelegateFunction!之后进行的,然后第二次调用匿名方法时候,才将this is the second DelegateFunction结果打印出来。上面的操作告诉我们,只有在调用匿名方法委托实例的时候才会执行匿名方法内部的操作。单纯的创建委托实例并不会立即执行匿名方法代码块。
3.匿名方法捕获的变量会延长变量的生存周期
对于被捕获的变量(localStr)只要有委托实例在引用它,它就会一直存在。
static void Main(string[] args) { MethodInvoker newDelegate = CreateDelegate(); newDelegate();//第二次输出This tempCount is 5 Console.Read(); } static MethodInvoker CreateDelegate() { int tempCount; MethodInvoker method = delegate { tempCount = 5; Console.WriteLine("This tempCount is {0}", tempCount); tempCount++; }; method();//第一次输出This tempCount is 5 return method; }
上面的代码输出的结果是两行:This tempCount is 5。在第二次调用的时候可以看到,正常来说tempCount是CreateDelegate()的局部变量,是在栈上的,在CreateDelegate()方法结束后tempCount会随着一起在栈中被销毁,然而在第二次调用匿名方法的时候tempCount的值会依然存在。秘密在于tempCount并不在栈上,事实上编译器创建了一个额外的类来存储变量,CreateDelegate()方法对该类有一个实例引用,所以它可以使用tempCount,而委托也对该实例有一个引用,除非委托准备好被GC回收,否则那个实例是不会被回收的,这样 出现上面的结果也就是正常的。
4.局部变量的“实例化”
我们对上面的代码进行简单的更改:
MethodInvoker newDelegate = CreateDelegate(); newDelegate();//This tempCount is 5 ;This tempOtherCount is 6 newDelegate();////This tempCount is 5 ;This tempOtherCount is 7 Console.Read(); static MethodInvoker CreateDelegate() { int tempOtherCount = 5; MethodInvoker method = delegate { int tempCount = 5; Console.WriteLine("This tempCount is {0}", tempCount); Console.WriteLine("This tempOtherCount is {0}", tempOtherCount); tempCount++; tempOtherCount++; }; method();//This tempCount is 5 ;This tempOtherCount is 5 return method; }
从上面的输出结果可以看出委托在捕捉变量的时候,要注意变量的位置,tempCount 和 我们在外部调用了两次委托实例,那相当于生成了两个不一样的tempCount实例,而tempOtherCount则是相同的一个实例,不同委托调用时,每个委托的tempCount实例是有区别的,而他们对tempOtherCount共用一个引用实例。在使用匿名方法的时候自己的小小疏忽可能会导致意想不到的结果。
以上是对匿名方法捕捉变量的简单举例,在实际问题中,使用捕获变量这种方法可以简化代码。以下是捕获变量的规则:
如果不用捕获变量的方法代码同样简单,就不用。
在循环体中要注意你是用的委托是否需要在循环迭代结束以后延续,以及是否想让它继续使用那个被捕捉变量的后续值,否则的话,就在循环内另外建一个变量来复制你想要 的值。
多委托时候要考虑是否这些委托要捕捉同一个变量。
如果被捕捉的变量不会发生改变,就不需要这么多的担心。
从垃圾回收的角度讲,思考任何捕获变量的被延长的生存期。这方面问题一般都不大,但是假如捕获的对象会产生昂贵的内存开销,这方面问题会凸现出来。(这点很重要)