一、 簡介
所有的方法都使用一個來自於相同集合的元素的子集。在C# 2.0中,可選元素集將會繼續增長。從歷史上看-除了C++內聯方法之外-方法都要求有一個名字、一個返回類型和一個方法體。而且可選擇地,方法可以使用存取修飾符和一個參數列表。在C# 2.0中,方法名已經從必需的變成了可選的。
C# 2.0(一般就代表.NET)引入了匿名方法。一個匿名方法可以被用在任何使用代理且該代理被定義為內聯的情況下,它不需要方法名,而具有可選的參數和一個方法體。
為了使用匿名方法,你需要了解什麼是代理。因此,在我們詳細討論何時使用匿名方法以及匿名方法的局限性之前,先讓我們簡要地回顧一下代理。
二、 代理回顧
匿名方法對於聲明和使用代理來說是一種壓縮方式(如果你對什麼是代理還有疑問,請繼續閱讀;否則,可以跳過下面的這一部分)。代理,作為一種指向函數簽名的指針,在.NET語言之前的語言中就已存在。切記,在計算機中一切其實都是位和字節。通過引入函數指針技術,有可能動態地把一些未來的目前尚未知的函數賦給指針,並由此誕生了事件。
函數指針的基本使用方法是,可以把一個函數的地址賦給一個單一的指針。為了通過一個指針來調用該函數,程序員要對之進行檢查以決定是否這個指針為null,然後間接地通過這個指針調用這個函數。總之,要使用指針,必須進行null檢查,而現在"一個指針對應一個函數"作為一種限制也該到結束的時候了。
回顧一下來分析,代理會成為原始函數指針的下一個進化替代者。一個代理即是一個類,它對該指針進行了封裝;隱含地,.NET中的代理是multicast代理。作為一個multicast代理僅僅意味著不再存在"一個函數對應一個指針"的限制,因為multicast代理類包含一個指針列表。包含一個內部列表意味著多於一個函數的地址可以被賦值給一個單一的代理。當該代理-你可以認為是"事件"-被激發或調用時,所有的內部列表函數被調用。
注意 在C#中,我們調用代理的方式就象從前我們調用方法以及調用所有的賦值函數一樣;但是我們仍然能夠進行null檢查。在Visual Basic.NET中,null檢查隱含在激活事件行為中。
在C#中,函數地址通過使用一個重載的+=操作符插入到一個列表中並且經由一個重載的-=操作符而被刪除。C#還支持手工地定義添加和刪除塊;添加和刪除對於代理恰似get和set對於屬性。
在C# 1.0和C# 1.1中,典型情況下,我們把代理實例賦給事件屬性。例如,在WinForms中,一個Button控件暴露一個Click事件。Click的代理類型是EventHandler。EventHandler是一個以對象和EventArgs為參數的方法。因此,我們可以用匹配代理EventHandler的簽名的任何方法來初始化一個EventHandler對象並且把代理賦給Click。下面是該代碼看上去的樣子:
private void Form1_Load(object sender, EventArgs e) { button1.Click += new EventHandler(OnClick);} private void OnClick(object sender, EventArgs e) { Debug.WriteLine("button1 clicked");}
因為WinForms的表單設計器和WebForms的頁面設計器自動地添加代理綁定;所以,我們有可能不需要手工式地綁定代理而建立大量的代碼。
三、 匿名方法是內聯代理
通常,當我們使用代理時,我們總是有一個方法。該方法的簽名匹配代理的簽名規定並且能被用來初始化一個代理實例。匿名方法用於把方法和代理的初始化壓縮到一個單一的位置。
通過使用前一節的例子,我們已看到代理new EventHandler的實例化是怎樣區別於用來初始化該代理的方法OnClick的。這部分代碼能被壓縮成一個匿名方法:
private void Form1_Load(object sender, EventArgs e){ button1.Click += delegate { Debug.WriteLine("button1 clicked"); }; }
為了創建該匿名方法,請注意我們刪除了OnClick的方法頭並且用OnClick的方法體的單詞delegate代替了EventHandler代理的構造器。其所導致的結果行為是相同的。如果我們想使用事件參數,我們通常與代理相關聯,我們可以在單詞delegate之後添加一可選的參數列表:
private void Form1_Load(object sender, EventArgs e){ button1.Click += delegate(object s, EventArgs ev) { Debug.WriteLine("object is " + s.ToString()); }; }
如果你定義代理參數,它們必須匹配代理類型所定義的參數。例如,Click的類型是EventHandler,因此如果參數存在,它們必須匹配EventHandler的參數對象和EventArgs。
匿名方法可以被使用在任何需要使用代理的地方。匿名方法可以使用ref和out參數,但是不能使用全局范圍的reference ref或out參數。匿名方法不能使用unsafe編碼,並且匿名方法不能以使得分支行為跳出匿名方法的代碼塊的方式來使用goto,break或continue等語句。
四、 市場調查結果
匿名方法是好東西嗎?市場調查證明匿名方法確實不錯,因為它們能夠減少由於實例化代理和減少分離方法所導致的代碼開銷。而且市場調查還證明匿名方法增強了可用性和可維護性。我認為良好命名的方法也可以實現這一點。請看下面的代碼容易維護嗎?
private void Form1_Load(object sender, EventArgs e) { BindClick(delegate { Debug.WriteLine("button1 click"); }); } private void BindClick(EventHandler handler) { button1.Click += handler; }
在這個例子中,我們把一個代理傳遞給一個方法-通過把該代理作為一個匿名方法傳遞。僅是保持圓括號、分號和方括號的順序和個數就已令人十分頭疼。
如果引用經典示例來說明,那就是匿名方法僅僅是因剔除了線程(它們使用代理)而減少了相應的創建代理和方法的開銷。這倒是真的,但是線程並不經常使用並且想正確使用也非常困難。我在想,要想使代碼更為秘密些而不是更為公開些該是多麼謹慎的一件事情。
就語言方面來講,我喜歡方法;但是作為一個實際開發中的事物,匿名方法也許僅是微軟的某個發明者有點太聰明的一種證明。
五、 總結
匿名方法是可以存在沒有名字的方法的證明-它們可以被定義並使用在任何能夠使用代理的地方。代理是事件處理器的包裝器。匿名方法到底有多大的實用性和普遍使用價值還有待於進一步的實踐證明。我懷疑,匿名方法將不會比運算符重載有更大的用途,並且其使用也會少之又少;但是匿名方法現在已是.NET的一部分,所以在閱讀代碼時能夠識別出它們來還是很有必要的。