最近看到一本書寫到關於匿名方法的使用,寫的比較深刻,今天在這里總結一下。由於第一次寫博客,如果有不妥的地方,請大家見諒,下面我們轉入正題:
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共用一個引用實例。在使用匿名方法的時候自己的小小疏忽可能會導致意想不到的結果。
以上是對匿名方法捕捉變量的簡單舉例,在實際問題中,使用捕獲變量這種方法可以簡化代碼。以下是捕獲變量的規則:
如果不用捕獲變量的方法代碼同樣簡單,就不用。
在循環體中要注意你是用的委托是否需要在循環迭代結束以后延續,以及是否想讓它繼續使用那個被捕捉變量的后續值,否則的話,就在循環內另外建一個變量來復制你想要 的值。
多委托時候要考慮是否這些委托要捕捉同一個變量。
如果被捕捉的變量不會發生改變,就不需要這么多的擔心。
從垃圾回收的角度講,思考任何捕獲變量的被延長的生存期。這方面問題一般都不大,但是假如捕獲的對象會產生昂貴的內存開銷,這方面問題會凸現出來。(這點很重要)