Closure in C#
0 |
今年因為jQuery的關係,對Javascript有較深入的研究(終於...),也認識了好用的Closure概念。
動態建立一個函數時把特定變數獨立保存一份,在特定場合裡是很犀利簡潔的解法,因此在進階Javascript程式開發中,Closure出現的機率還蠻高的。那麼,.NET, C#呢? 也可以做到Closure嗎?
在C#世界裡,對應Javascript的var myFunc = function(s) { alert(s); }這類變數結合匿名函數的概念,一般是用delegate加上匿名方法解決。
在C#裡要實踐Closure也很簡單,在匿名方法中直接呼叫外部的變數就可以了。若該變數屬區域性(Local),你會發現匿名方法在建立時會為這些區域變數建立一個複本供自己專用;反之,若變數並非區域性的,則會是多個匿名方法共用。
我試著用以下程式碼來示範。透過迴圈我產生了四個匿名方法放入funcs陣列中,匿名方法裡則大方地呼叫了intVar及outVar,其中intVar的存在範圍只限於for迴圈內,符合區域變數的定義。而在匿名方法中,每呼叫一次就增加一次intVar的值,依前段說明,funcs[0]會有自己的intVar,初始值為0;funcs[1]也有自己的intVar,初始值為1。而outVar宣告於for迴圈之外,funcs[0 – 4]共用一個,一修改數值,四個匿名方法都會被影響。
using System;
class Program
{
delegate void MyFunc(string paramStr);
static void Main(string[] args)
{
MyFunc[] funcs = new MyFunc[4];
int outVar = 5;
for (int i = 0; i < 4; i++)
{
int intVar = i;
funcs[i] = (s) =>
{
Console.WriteLine(
"outVar = {0} intVar = {1} paramStr = {2}",
outVar, intVar, s);
intVar++;
};
}
funcs[0]("First Call");
funcs[0]("Second Call");
funcs[2]("Test1");
funcs[3]("Test2");
outVar = 6;
funcs[2]("Test3");
funcs[3]("Test4");
Console.Read();
}
}
執行結果為:
outVar = 5 intVar = 0 paramStr = First Call
outVar = 5 intVar = 1 paramStr = Second Call
outVar = 5 intVar = 2 paramStr = Test1
outVar = 5 intVar = 3 paramStr = Test2
outVar = 6 intVar = 3 paramStr = Test3
outVar = 6 intVar = 4 paramStr = Test4
我們來試著解釋以上結果。在First Call裡,funcs[0]的intVar還是初始值0,在Console.WriteLine後intVar++,因此在Second Call時,intVar就變成1了。而Test1中,funcs[2]有自己獨立的intVar,不受剛才funcs[0]呼叫的影響,顯示出初始值2,Test2裡funcs[3]則顯示intVar初始值3。到目前為止所有funcs[*]的outVar都是5。接著我們改變outVar=6,在Test3, Test4裡再次呼叫funcs[2], funcs[3],一如預期分別得到遞增後的intVar--3與4,而outVar則顯示新值6。
很有趣吧! 這是怎麼做到的? 其實Compiler在背後做了一大堆工作,大顆汗小顆汗才實踐了這番效果。大致原理是Compiler自行宣告了一個內部使用的Class(假設叫ToToMoMoClass),並宣告了一個intVar作為Class Memeber,接著為每個匿名方法建立一個ToToMoMoClass Instance,由於有4個Instance,就實踐了每個匿名方法都有自己一份intVar的效果。以上只是概念化的解釋,實踐做法裡還有不少深奧細節,有興趣深入的話可以參考延伸閱讀中的文章,有較詳盡的說明。
【延伸閱讀】
Comments
Be the first to post a comment