http://www.microsoft.com/china/msdn/library/langtool/vcsharp/CreElegCodAnymMeth.mspx?mfr=true
C# 支援用于調用一個或多個方法的委托 (delegate)。委托提供運算符和方法來添加或删除目标方法,它也可以在整個 .NET 架構中廣泛地用于事件、回調、異步調用、多線程等。然而,僅僅為了使用一個委托,有時您不得不建立一個類或方法。在這種情況下,不需要多個目标,并且調用的代碼通常相對較短而且簡單。在 C# 2.0 中,匿名方法是一個新功能,它允許定義一個由委托調用的匿名(也就是沒有名稱的)方法。
例如,下面是一個正常 SomeMethod 方法的定義和委托調用:
可以用一個匿名方法來定義和實作這個方法:
匿名方法被定義為内嵌 (in-line) 方法,而不是作為任何類的成員方法。此外,無法将方法屬性應用到一個匿名方法,并且匿名方法也不能定義一般類型或添加一般限制。
您應該注意關于匿名方法的兩件值得關注的事情:委托保留關鍵字的重載使用和委托指派。稍後,您将看到編譯器如何實作一個匿名方法,而通過檢視代碼,您就會相當清楚地了解編譯器必須推理所使用的委托的類型,執行個體化推理類型的新委托對象,将新的委托包裝到匿名方法中,并将其指派給匿名方法定義中使用的委托(前面的示例中的 del)。
匿名方法可以用在任何需要使用委托類型的地方。您可以将匿名方法傳遞給任何方法,隻要該方法接受适當的委托類型作為參數即可:
如果需要将一個匿名方法傳遞給一個接受抽象 Delegate 參數的方法,例如:
則首先需要将匿名方法強制轉換為特定的委托類型。
下面是一個将匿名方法作為參數傳遞的具體而實用的示例,它在沒有顯式定義 ThreadStart 委托或線程方法的情況下啟動一個新的線程:
在前面的示例中,匿名方法被當作線程方法來使用,這會導緻消息框從新線程中顯示出來。
傳回頁首
當定義帶有參數的匿名方法時,應該在 delegate 關鍵字後面定義參數類型和名稱,就好像它是一個正常方法一樣。方法簽名必須與它指派的委托的定義相比對。當調用委托時,可以傳遞參數的值,與正常的委托調用完全一樣:
如果匿名方法沒有參數,則可以在 delegate 關鍵字後面使用一對空括号:
然而,如果您将 delegate 關鍵字與後面的空括号一起忽略,則您将定義一種特殊的匿名方法,它可以指派給具有任何簽名的任何委托:
明顯地,如果匿名方法并不依賴于任何參數,而且您想要使用這種與委托簽名無關的方法代碼,則您隻能使用這樣的文法。注意,當調用委托時,您仍然需要提供參數,因為編譯器為從委托簽名中推理的匿名方法生成無名參數,就好像您曾經編寫了下面的代碼(在 C# 僞碼中)一樣:
此外,不帶參數清單的匿名方法不能與指出參數的委托一起使用。
匿名方法可以使用任何類成員變量,并且它還可以使用定義在其包含方法範圍之内的任何局部變量,就好像它是自己的局部變量一樣。圖 7 對此進行了展示。一旦知道如何為一個匿名方法傳遞參數,也就可以很容易地定義匿名事件處理,如圖 8 所示。
因為 += 運算符僅僅将一個委托的内部調用清單與另一個委托的内部調用清單連接配接起來,是以可以使用 += 來添加一個匿名方法。注意,在匿名事件處理的情況下,不能使用 -= 運算符來删除事件處理方法,除非将匿名方法作為處理程式加入,要這樣做,可以首先将匿名方法存儲為一個委托,然後通過事件注冊該委托。在這種情況下,可以将 -= 運算符與相同的委托一起使用,來取消将匿名方法作為處理程式進行注冊。
編譯器為匿名方法生成的代碼很大程度上依賴于匿名方法使用的參數或變量的類型。例如,匿名方法使用其包含方法的局部變量(也叫做外部變量)還是使用類成員變量和方法參數?無論是哪一種情況,編譯器都會生成不同的 MSIL。如果匿名方法不使用外部變量(也就是說,它隻使用自己的參數或者類成員),則編譯器會将一個私有方法添加到該類中,以便賦予方法一個唯一的名稱。該方法的名稱具有以下格式:
和其他編譯器生成的成員一樣,這都是會改變的,并且最有可能在最終版本釋出之前改變。方法簽名将成為它指派的委托的簽名。
編譯器隻是簡單地将匿名方法定義和指派轉換成推理委托類型的标準執行個體,以包裝機器生成的私有方法:
非常有趣的是,機器産生的私有方法并不顯示在 IntelliSense 中,也不能顯式地調用它,因為其名稱中的美元符号對于 C# 方法來說是一個非法标記(但它是一個有效的 MSIL 标記)。
當匿名方法使用外部變量時,情況會更加困難。如果這樣,編譯器将用下面的格式添加具有唯一名稱的私有嵌套類:
嵌套類有一個名為 <this> 的指向包含類的引用,它是一個有效的 MSIL 成員變量名。嵌套類包含與匿名方法使用的每個外部變量對應的公共成員變量。編譯器向嵌套類定義中添加一個具有唯一名稱的公共方法,格式如下:
方法簽名将成為被指派的委托的簽名。編譯器用代碼替代匿名方法定義,此代碼建立一個嵌套類的執行個體,并進行必要的從外部變量到該執行個體的成員變量的指派。最後,編譯器建立一個新的委托對象,以便包裝嵌套類執行個體的公共方法,然後調用該委托來調用此方法。圖 9 用 C# 僞代碼展示了編譯器為圖 7 中定義的匿名方法生成的代碼。
匿名方法可以使用一般參數類型,就像其他方法一樣。它可以使用在類範圍内定義的一般類型,例如:
因為委托可以定義一般參數,是以匿名方法可以使用在委托層定義的一般類型。可以指定用于方法簽名的類型,在這種情況下,方法簽名必須與其所指派的委托的特定類型相比對:
雖然乍一看匿名方法的使用可能像一種另類的程式設計技術,但是我發現它是相當有用的,因為在隻要一個委托就足夠的情況下,使用它就可以不必再建立一個簡單方法。圖 10 展示了一個有用的匿名方法的實際例子 — SafeLabel Windows 窗體控件。
Windows 窗體依賴于基本的 Win32 消息。是以,它繼承了典型的 Windows 程式設計要求:隻有建立視窗的線程可以處理它的消息。在 .NET 架構 2.0 中,調用錯誤的線程總會觸發一個 Windows 窗體方面的異常。是以,當在另一個線程中調用窗體或控件時,必須将該調用封送到正确的所屬線程中。Windows 窗體有内置的支援,可以用來擺脫這個困境,方法是用 Control 基類實作 ISynchronizeInvoke 接口,其定義如下:
Invoke 方法接受針對所屬線程中的方法的委托,并且将調用從正在調用的線程封送到該線程。因為您可能并不總是知道自己是否真的在正确的線程中執行,是以通過使用 InvokeRequired 屬性,您可以進行查詢,進而弄清楚是否需要調用 Invoke 方法。問題是,使用 ISynchronizeInvoke 将會大大增加程式設計模型的複雜性,是以較好的方法常常是将帶有 ISynchronizeInvoke 接口的互動封裝在控件或窗體中,它們會自動地按需使用 ISynchronizeInvoke。
例如,為了替代公開 Text 屬性的 Label 控件,您可以定義從 Label 派生的 SafeLabel 控件,如圖 10 所示。SafeLabel 重寫了其基類的 Text 屬性。在其 get 和 set 中,它檢查 Invoke 是否是必需的。如果是這樣,則它需要使用一個委托來通路此屬性。該實作僅僅調用了基類屬性的實作,不過是在正确的線程上。因為 SafeLabel 隻定義這些方法,是以它們可以通過委托進行調用,它們是匿名方法很好的候選者。SafeLabel 傳遞這樣的委托,以便将匿名方法作為其 Text 屬性的安全實作包裝到 Invoke 方法中。