委托、事件與匿名方法 — 學習委托最好的資料


Delegates, Events, and Anonymous Methods
委托、事件與匿名方法

譯者注:委托、事件和匿名方法等在C#編程中有廣泛運用,也有很多資料和書籍對它們做過大量介紹,但在我接觸的人群中仍有很多人對它們還不甚了解,甚至懼怕。我希望這篇博文能夠把這些東西說清講透,也希望有此需要的園友在閱讀之后能夠獲得對它們的深刻理解,並在今后的編程生涯中熟練地運用它們。還希望這篇博文成為介紹委托的經典的技術文章。

本文的主體內容譯自《Introducing Visual C# 2010》(Adam Freeman著,Apress出版)一書的第10章。在譯文中用“譯者注”對原文作了一些補充說明,以使讀者對相關內容有更清醒的認識或更明確的概念。

這是一篇篇幅不短的文章,如能認真閱讀,相信一定會有所收獲,並從此不再懼怕!

Delegates are special types that encapsulate a method, similar to function pointers found in other programming languages. Delegates have a number of uses in C#, but you are most likely to encounter a special type of delegate—the event. Events make notifying interested parties simple and flexible, and I’ll explain the convention for their use later in the chapter.
委托是封裝方法的特殊類型,它類似於其它編程語言中的函數指針。委托在C#中有大量運用,但你最可能遇到的是一種特殊的委托類型 — 事件。事件使得對相關部件的通知變得簡單而靈活,本章后面將解釋其使用約定。

I’ll also explain the Func and Action types that let you use delegates in a more convenient form and that are used extensively in some of the latest C# language features, such as parallel programming. We’ll finish up this chapter with a look at anonymous methods and lambda expressions, two C# features that let us implement delegates without having to define methods in our classes. Table 10-1 provides the summary for this chapter
我也會解釋Func和Action類型,它們讓你以更方便的形式使用委托,而且在一些最新的C#特性中也有廣泛使用,如並行編程。本章最后考察匿名方法和lambda表達式,這是讓我們不必在類中定義方法就可以使用委托的兩個C#特性。表10-1提供了本章概要。

以上這兩段文字告訴我們:委托是一種封裝方法的特殊類型。事件是特殊形式的委托,用以實現對相關部件的通知。FuncAction是C#的兩個特殊類型,使我們能夠更方便地使用委托。匿名方法Lambda表達式是C#的兩個特性,讓我們不必定義方法就可以使用委托。 — 譯者注

Using Delegates
使用委托

A delegate is a special C# type that represents a method signature. Methods are discussed in Chapter 9, and the signature is the combination of the return type and the type and order of the method parameters. Listing 10-1 contains an example of a delegate.
委托delegate)是表示方法簽名的一種特殊的C#類型。方法在第9章討論過,而方法簽名是方法的返回類型和方法參數的類型及其順序的組合。清單10-1是一個委托示例。

以上是委托的概念定義。很多人都知道委托是用來關聯方法的,但未能強烈意識到,委托更主要的是定義和使用一種類型。既然是一種類型,委托的使用便與類的使用具有類似性。因此,關於委托的使用通常應當包含這樣幾個環節:定義委托、創建委托對象、委托對象實例化、執行或調用委托。

另外要特別注意的是,關於委托的名詞是混淆的。委托定義、委托對象、委托實例、以及委托調用等,有時不作明確的區分,都籠統地叫做委托。因此,在委托的使用過程中,必須從概念上分清什么是委托定義、委托對象、委托實例、以及什么是執行或調用委托。 — 譯者注

Listing 10-1. Defining a Delegate Type
清單10-1. 定義一個委托類型

public delegate int PerformCalc(int x, int y);

There are five parts to the delegate in Listing 10-1, and they are illustrated in Figure 10-1.
清單10-1所示的委托有五個部分,它們如圖10-1所示。

IntrC10-1

1. Access Modifier — 訪問修飾符 2. Delegate Keyword — delegate關鍵字 3. Result Type — 結果類型 4. Delegate Name — 委托名 5. Parameters — 參數

Figure 10-1. The anatomy of a delegate
圖10-1. 一個委托的剖析

The first two parts of a delegate are simple. First, all delegates require the delegate keyword. Second, delegates, like all types, can have access modifiers. See Chapter 6 for a description of how these modifiers apply to classes; they have the same effect on delegates.
委托的前兩個部分很簡單。首先,所有委托都需要delegate關鍵字。其次,像所有類型一樣,委托可以有訪問修飾符。參見第6章如何把這些修飾符運用於類的描述,它們在委托上有同樣的效果。

The delegate name is the name by which we will refer to the type we have created. This is equivalent to the class name. The name of the delegate type in Listing 10-1 is PerformCalc.
委托名是用來指向已創建的這個類型的名稱。它等同於類名。清單10-1中的委托類型名是PerformCalc。

The remaining parts of the delegate specify the kind of method that instances of this delegate can represent. In Listing 10-1, instances of the delegate can represent methods that return an int and that have two int parameters.
該委托的其余部分指明了這個委托的實例可以代表的方法的種類。在清單10-1中,委托的實例可以代表返回一個int(整數)且有兩個int參數的所有方法。

As we look at each part of the delegate in Listing 10-1, it is important to bear in mind that when we define a new delegate, we are defining a new type. What we are saying is, “Here is a new type that can be used to refer to a specific kind of method.” Delegates can be hard to understand, and if you find yourself getting lost in this chapter, you should come back to the previous sentence. You can define a new delegate type in the same places as you can create a new class—in a namespace, class, or struct. Once we have defined a new delegate type, we can create an instance of it and initialize it with a value. Listing 10-2 contains a demonstration.
在我們考察清單10-1中委托的各個部分時,重要的是記住:定義一個新委托時,實際是在定義一個新類型。就好像在說:“這是一個新類型,它可以用來指向一類特定的方法”。委托可能難以理解,但如果在本章中發現自己迷失了方向,你應該回想上面這句話。就像可以定義一個新類一樣,你可以在定義類的那些地方定義一個新的委托類型 — 在命名空間、類、或結構中。一旦定義了一個新的委托類型,就可以創建它的實例,並用一個值對它初始化。清單10-2是一個演示。

Listing 10-2. Defining a Delegate Field
清單10-2. 定義一個委托字段

// 定義一個委托
public delegate int PerformCalc(int x, int y);
class Calculator { // 委托類型字段,用以創建委托對象 PerformCalc perfCalc; // 無訪問修飾符的字段意為private(私有)
// 構造器 public Calculator() { // 實例化委托對象。 // 對委托對象進行實例化的辦法是,用一個方法名對委托對象進行賦值。 // 於是,以下語句的含義為,perfCalc委托是對CalculateProduct方法的引用 perfCalc = CalculateProduct; }
// 屬性,用以暴露委托對象 public PerformCalc CalcDelegate { get { return perfCalc; } }
// 方法,與委托類型具有相同的方法簽名,用以對委托對象實例化 private int CalculateProduct(int num1, int num2) { return num1 * num2; } }

The Calculator class in Listing 10-2 has a field called perfCalc that is of the type of delegate we defined in Listing 10-1. This has created a field that can be used to represent a method that matches the delegate, in other words, a method that returns an int and has two int parameters. The Calculator class contains a method called CalculateProduct that matches that description, and in the Calculator constructor, I assign a value to the delegate field by using the name of the matching method. The definition of the field and the assignment of a value are shown in bold.
清單10-2中的Calculator類有一個叫做perfCalc的字段,它的類型是清單10-1中所定義的委托類型。它創建了一個字段,可以用來表示與委托匹配的方法,即,一個返回int並有兩個int參數的方法。Calculator類有一個叫做CalculateProduct的方法與這個描述相匹配,而且在Calculator構造器中,通過使用這個匹配的方法名給這個委托字段賦了一個值。這個字段的定義和賦值以黑體顯示。

The Calculator class in Listing 10-2 also contains a public property that returns an instance of the delegate type. The accessor in the property returns the value assigned to the delegate field.
清單10-2中的Calculator類還有一個public屬性,它返回委托類型的一個實例。這個屬性的訪問器(指屬性的getter塊 — 譯者注)返回賦給委托字段的值。

Now we have a new delegate type, PerformCalc, instances of it can be used to represent methods that return an int and that have two int parameters. We have a Calculator class that has a private field of the PerformCalc type and that has been assigned the CalculateMethod and a public property that returns the value of the delegate field. Listing 10-3 demonstrates how to use the delegate.
現在,我們有了一個新的委托類型PerformCalc。它的實例可以用來表示返回一個int並有兩個int參數的方法。有一個Calculator類,它有一個PerformCalc類型的private(私有)字段,並把CalculateMethod(應當是CalculateProduct — 譯者注)賦給了它。還有一個public屬性,它返回該委托字段的值。清單10-3演示了如何使用這個委托。

Listing 10-3. Using a Delegate Obtained Through a Property
清單10-3. 使用通過屬性獲得的委托

class Listing_03 {
    static void Main(string[] args) {
        Calculator calc = new Calculator();
// get the delegate // 獲取委托 PerformCalc del = calc.CalcDelegate;
// invoke the delegate to get a result // 調用該委托以獲得結果 int result = del(10, 20);
// print out the result // 打印結果 Console.WriteLine("Result: {0}", result);
// wait for input before exiting // 退出之前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); } }

A new instance of the Calculator class is created, and the CalcDelegate property is used to assign a value to a local variable of the PerformCalc delegate type; this means that the del variable contains a reference to the CalculateProduct method in the Calculator object. I invoke the delegate with the following statement:
上述代碼創建了Calculator類的一個新實例,用CalcDelegate屬性把一個值賦給了PerformCalc委托類型的一個局部變量,這意味着del變量是對Calculator對象中CalculateProduct方法的引用。用以下語句調用這個委托:

int result = del(10, 20);

This statement passes the parameters 10 and 20 to the method assigned to the del variable, which means that the CalculateProduct method in the Calculator object is called. The result from the delegated method is assigned to the local result variable, just as would happen with a regular method call.
這條語句把參數10和20傳遞給賦給del變量的方法,這意味着調用Calculator對象中的CalculateProduct方法。從這個委托方法而來的結果被賦值給局部變量result,這就像調用一個常規方法所發生的情況一樣。(可見,調用委托就像執行常規的方法一樣 — 譯者注)

總結上述委托編程過程,可以從概念上形成以下幾個名詞:

  1. 委托定義:委托定義的作用是創建一個能夠封裝一類方法的類型。
  2. 委托字段:在委托編程中需要有一個委托字段,該字段可以用來創建委托對象。也可以把這個委托字段看成為是一個委托類型的變量(簡稱為委托變量)。於是,隨時可以用一個方法對這個委托變量進行賦值。
  3. 委托實例:這是對委托字段的實例化,以形成委托對象。實例化的辦法是用一個具有相同簽名的方法名對委托變量進行賦值。實例化的作用是把委托字段與實例化方法關聯在一起,形成委托對象。因此,無論何時,調用委托就是執行其實例化方法。
  4. 委托屬性:可以用委托屬性對外暴露委托對象。
  5. 執行/調用委托:執行或調用委托實際上是執行委托對象的實例化方法,執行/調用委托與執行規則的方法一樣(送入方法參數、接收返回結果)。 — 譯者注

The reason that I created a new Calculator object is that I wanted to delegate an instance method, and you can do that only once you have an instance to work with. If you want to delegate a static method, then you can do so by using the class name; you can see an example of delegating a static method in Listing 10-4 later in the chapter.
創建一個新的Calculator對象的原因是想委托一個實例方法,而且你只要這樣做一次,就有了一個用來進行工作的實例。如果想委托一個靜態方法,那么可以用這個類名來做,本章稍后可以看到委托一個靜態方法的示例。

You can also use generic types with delegates. If we wanted a generic version of the delegate type defined in Listing 10-1, we could define the following:
也可以使用委托的泛型類型。如果想定義類似於清單10-1所示的委托類型的泛型類型,可以這樣定義:

public delegate T PerformCalc<T>(T x, T y);

Then to create the delegate field in the Calculator class, we would use the following:
然后創建Calculator類中的委托字段,像這樣:

class Calculator {
    PerformCalc<int> perfCalc;
    ...

In this way, we can define a general-purpose delegate and provide the type parameters as needed when we define the delegate fields or variables. Generic types are described in Chapter 15.
通過這種方式,我們可以定義一個通用目的的委托,並在定義委托字段或變量時,提供必要的類型參數。泛型類型在第15章描述。

There are a couple of points to note about all the examples so far. The first is that we passed around a method as we would a regular variable, invoking it only when we needed. The other is that the class that called the delegated method had no direct relationship to the method being invoked. We delegated a private method hidden away inside the Calculator class that the Listing_03 class wouldn’t otherwise be able to access.
對於上述這些例子,有兩點需要注意。第一點是我們像傳遞常規的變量一樣來傳遞一個方法,只在需要的時候調用它(可見,我們可以像對變量賦值一樣,把一個方法賦給一個委托,還可以像變量一樣對委托進行傳遞。於是,通過委托使得對方法的傳遞變得十分靈活 — 譯者注)。另一點是調用委托方法的類與被請求的方法沒有直接的關系(從而實現了方法的使用與方法的具體實現之間的分離。於是,方法體可以被重構或被替換 — 譯者注)。我們委托了在Calculator類中被隱藏起來的一個私有方法,這個方法因而在Listing_03類中是不能訪問的。

The examples have shown how to use delegates but didn’t really explain why you might find them useful. In the following sections, I’ll show you ways to use delegates that simplify common coding patterns and demonstrate some useful C# features.
這些例子演示了如何使用委托,但並未解釋為什么它們是有用的。在以下小節中,將演示使用委托的方式,以達到簡化常規的編碼模式,並演示一些有用的C#特性。

Using Delegates for Callbacks
用委托進行回調

You can use delegates to create callbacks, where one object is notified when something of interest happens in another object. Listing 10-4 contains an example of a simple callback to notify an interested class when a calculation is performed.
可以用委托來創建回調callback),即,當在一個對象中發生某個關心的事情時,通知另一個對象。清單10-4是一個簡單回調的示例,以便在計算完成時通知一個感興趣的類。

Listing 10-4. Using a Delegate for a Callback
清單10-4. 使用委托進行回調

using System; 
// 委托字義 delegate void NotifyCalculation(int x, int y, int result);
class Calculator { // 委托字段 NotifyCalculation calcListener;
// 構造器,用參數給委托字段賦值(委托實例化) — 譯者注 public Calculator(NotifyCalculation listener) { calcListener = listener; }
// 方法,計算兩數乘積,在其中通過委托向另一對象發送通知 — 譯者注 public int CalculateProduct(int num1, int num2) { // perform the calculation // 執行計算 int result = num1 * num2;
// notify the delegate that we have performed a calc // 向委托發送通知,說明已經執行了一個計算 calcListener(num1, num2, result);
// return the result // 返回結果 return result; } }
// 接收通知的類 — 譯者注 class CalculationListener { // 與委托的方法簽名對應的方法。 // 注意,這是一個靜態方法,因此不需要在這個類中創建委托字段並實例化, // 可以直接通過類名返回該方法的一個實例,如Calculation.CalculationPrinter — 譯者注 public static void CalculationPrinter(int x, int y, int result) { Console.WriteLine("Calculation Notification: {0} x {1} = {2}", x, y, result); } }
// 主程序 class Listing_04 { static void Main(string[] args) { // create a new Calculator, passing in the printer method // 創建一個新的Calculator,在其中傳遞printer方法 // 於是把一個方法對象注入到了Calculator對象的calc之中形成了一個委托實例 — 譯者注 Calculator calc = new Calculator(CalculationListener.CalculationPrinter);
// perform some calculations // 執行一些計算 // 注意,每執行一次計算,在計算體里都會回調委托向CalculationListener發送一個通知 // 通知的作用導致執行CalculationListener中的printer方法 — 譯者注 calc.CalculateProduct(10, 20); calc.CalculateProduct(2, 3); calc.CalculateProduct(20, 1);
// wait for input before exiting // 退出前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); } }

回調是事件的基礎,請通過上述清單真正理解委托回調:首先把一個接收通知的方法注入到一個對象(通知源對象,也即發出通知的對象)之中(上例中的黑體語句),以形成通知源中的委托實例。然后在通知源對象的某個方法中,通過執行這個委托實例,以便回過頭來調用(回調)這個被委托方法,從而實現向目標對象(或目標方法)發送通知的目的。因此,所謂委托回調是指通過委托實現回調 — 譯者注

The delegate type in this example is called NotifyCalculation and has parameters for the two numbers that have been used for the calculation and the result that was computed. The Calculator class in this example has a constructor argument that takes an instance of the delegate type, which is then invoked inside the CalculateProduct method.
此例中的委托類型叫做NotifyCalculation,其參數是用於計算的兩個數字和計算所得的結果。這個例子中的Calculator類有一個構造器參數,它是委托類型的一個實例,在CalculatreProduct方法中對這個實例進行調用。

The Listing_04 class creates a new instance of Calculator and passes a reference to the static CalculationListener.CalculationPrinter method as the constructor parameter. The delegate is called each time the Calculator.CalculateProduct method is invoked, printing out a notification of the calculation that has been performed. Compiling and running the code in Listing 10-4 produces the following result:
Listing_04類創建了一個新的Calculator實例,並把對靜態方法CalculationListener.CalculationPrinter的引用作為構造器參數進行傳遞。每次請求Calculator.CalculateProduct方法都會調用這個委托,打印出一個計算已被執行的通知,編譯並運行清單10-4中的代碼產生以下結果:

Calculation Notification: 10 x 20 = 200
Calculation Notification: 2 x 3 = 6
Calculation Notification: 20 x 1 = 20
Press enter to finish(按回車鍵結束)

Using delegates in callbacks means that the source of the notifications doesn’t need to know anything about the class that receives them, allowing the notification receiver to be refactored or replaced without the source having to be modified at all. As you’ll see in the “Delegating Selectively” section later in the chapter, we can select a delegate at runtime, which provides us with even greater flexibility.
在回調中使用委托,意味着通知源不需要知道通知接收者的任何信息,這允許通知接受者被重構或被替換,而源根本不需要做任何修改。正如你將在稍后的“選擇性委托”小節中所看到的,可以在運行時選擇一個委托,這為我們提供了更大的靈活性。

Multicasting with Delegates
委托多播

When performing callbacks, you will often need to cater for multiple listeners, rather than the single listener shown in Listing 10-4. The delegate type uses custom + and – operators that let you combine several method references together into a single delegate and invoke them in one go, known as multicasting. Custom operators are discussed in Chapter 8. Listing 10-5 contains an example of a multicasting delegate callback.
在執行回調時,通常需要迎合多個偵聽器(通知接收者 — 譯者注),而不是如清單10-4所示的一個單一的偵聽器。委托類型采用 + 和 – 操作符,允許你把對幾個方法的引用結合在一起,形成一個單一的委托,並一次性地調用它們,這稱為多播multicasting)(因此,多播是一次性調用委托,達到向多個目標發送通知的目的 — 譯者注)。自定義操作符在第8章討論過。清單10-5是一個多播委托回調的例子。

Listing 10-5. Using Delegate Multicasting
清單10-5. 使用委托多播

using System; 
// 委托定義 delegate void NotifyCalculation(int x, int y, int result);
class Calculator { // 委托字段 NotifyCalculation calcListener;
// 方法,增加委托 public void AddListener(NotifyCalculation listener) { calcListener += listener; }
// 方法,去除委托 public void RemoveListener(NotifyCalculation listener) { calcListener -= listener; }
// 方法,執行乘積計算 public int CalculateProduct(int num1, int num2) { // perform the calculation // 執行計算 int result = num1 * num2;
// notify the delegate that we have performed a calc // 通知委托,告知已執行了一個計算 calcListener(num1, num2, result);
// return the result // 返回結果 return result; } }
// 偵聽器類 class CalculationListener { // 字段,表示偵聽器id private string idString;
// 構造器,設置偵聽器id public CalculationListener(string id) { idString = id; }
// 與委托簽名對應的方法,調用委托時將執行此方法 public void CalculationPrinter(int x, int y, int result) { Console.WriteLine("{0}: Notification: {1} x {2} = {3}", idString, x, y, result); } }
// 另一個偵聽器類 class AlternateListener { public static void CalculationCallback(int x, int y, int result) { Console.WriteLine("Callback: {0} x {1} = {2}", x, y, result); } }
// 主程序 class Listing_05 { static void Main(string[] args) { // create a new Calculator // 創建一個新計算器 Calculator calc = new Calculator();
// create and add listeners // 創建並添加偵聽器,這稱為訂閱通知 — 譯者注 calc.AddListener(new CalculationListener("List1").CalculationPrinter); calc.AddListener(new CalculationListener("List2").CalculationPrinter); calc.AddListener(AlternateListener.CalculationCallback);
// perform a calculation // 執行一個計算 calc.CalculateProduct(10, 20);
// remove a listener // 移去一個偵聽器,這稱為退訂通知 — 譯者注 calc.RemoveListener(AlternateListener.CalculationCallback);
// perform a calculation // 執行一個計算 calc.CalculateProduct(10, 30);
// wait for input before exiting // 退出之前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); } }

The Calculator class in this example defines two methods that register and unregister callback delegates using the += and -= operators. There are two classes that contain methods that match the delegate signature, CalculationListener and AlternateListener, and the Listing_05 class registers and unregisters the methods as delegates with the Calculator object. You can see that you use a multicast delegate just as you would a single delegate. Compiling and running the code in Listing 10-5 produces the following results:
此例中的Calculator類定義了兩個方法,用 += 和 -= 操作符注冊和注銷回調委托。有兩個類含有與委托簽名匹配的方法,CalculationListener和AlternateListener,而Listing_05類以Calculator對象來注冊和注銷委托方法。可以看出,使用多播委托就像使用單個委托一樣。編譯並運行清單10-5代碼會產生如下結果:

List1: Notification: 10 x 20 = 200
List2: Notification: 10 x 20 = 200
Callback: 10 x 20 = 200
List1: Notification: 10 x 30 = 300
List2: Notification: 10 x 30 = 300
Press enter to finish

可以通過以下描述體會委托多播的價值:假設我們要做一個多國語言翻譯機,把一國文字翻譯成多個國家的文字。於是可以把英-漢、英-日、英-俄、英-德等多個翻譯方法組合成一個委托多播,然后通過一次性委托調用得到全部翻譯結果 — 譯者注

Delegating Selectively
選擇性委托

One of the benefits of being able to pass delegates around as variables is to apply delegates selectively, such that we create a delegate that is tailored to a given situation. Listing 10-6 contains a simple example.
能夠把委托像變量一樣傳遞的一個好處是可以有選擇地運用委托,這樣,我們可以針對一個給定的情況來創建委托。清單10-6是一個簡單的例子。

Listing 10-6. Creating Anonymous Delegates Based on Parameter Value
清單10-6. 創建基於參數值的匿名委托

using System; 
// 委托定義 delegate int PerformCalc(int x, int y);
// 計算器類 class Calculator { public enum Modes { Normal, Iterative };
public PerformCalc GetDelegate(Modes mode) { if (mode == Modes.Normal) { return CalculateNormally; } else { return CalculateIteratively; } }
// 與委托簽名一致的方法,乘積 private int CalculateNormally(int x, int y) { return x * y; }
// 與委托簽名一致的方法,累加 private int CalculateIteratively(int x, int y) { int result = 0; for (int i = 0; i < x; i++) { result += y; } return result; } }
// 主程序 class Listing_06 { static void Main(string[] args) { // create a new Calculator // 創建一個新Calculator Calculator calc = new Calculator();
// get a delegate // 獲取一個委托 PerformCalc del = calc.GetDelegate(Calculator.Modes.Normal);
// use the delegate // 使用該委托 Console.WriteLine("Normal product: {0}", del(10, 20));
// get a delegate // 獲取一個委托 del = calc.GetDelegate(Calculator.Modes.Iterative);
// use the delegate // 使用該委托 Console.WriteLine("Iterative product: {0}", del(10, 20));
// wait for input before exiting // 退出之前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); } }

The Calculator class in Listing 10-6 has a GetDelegate method that returns an delegate based on the parameter value, selected from an enum. If the parameter is the Normal enum value, the delegate returned by the method uses the standard C# multiplication operator, but if the value is Iterative, then the method returns a delegate that performs multiplication as an iterative series of additions.
清單10-6中的Calculator類有一個GetDelegate方法,它返回一個基於參數值的、從一個enum(枚舉)選擇的委托。如果參數是Normal枚舉值,由方法返回的委托使用標准的C#乘法運算符,但如果該值是Iterative,那么該方法返回另一種委托,它像反復累加那樣執行乘法。

Interrogating Delegates
質詢委托

The base type for all delegates is System.Delegate, and we can use the members of this class to find out which methods a delegate will invoke on our behalf. Listing 10-7 contains an example.
所有委托的基類型都是System.Delegate,而且可以使用該類的成員為我們找出一個委托所調用的方法。清單10-7是一個示例。

Listing 10-7. Interrogating Delegate Types
清單10-7. 質詢委托類型

using System; 
delegate int PerformCalc(int x, int y);
class Calculator { public int CalculateSum(int x, int y) { return x + y; } }
class AlternateCalculator { public int CalculateProduct(int x, int y) { return x * y; } }
class Listing_07 { static void Main(string[] args) { // create a delegate variable // 創建委托變量 PerformCalc del = new Calculator().CalculateSum;
// combine with another method // 組合另一個方法 del += new AlternateCalculator().CalculateProduct;
// Interrogate the delegate // 質詢委托 Delegate[] inlist = del.GetInvocationList(); foreach (Delegate d in inlist) { Console.WriteLine("Target: {0}", d.Target); Console.WriteLine("Method: {0}", d.Method); }
// wait for input before exiting // 退出之前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); } }

The Listing_07 class creates a new delegate variable and uses it to combine methods from the Calculator and AlternateCalculator classes. I use the GetInvocationList method on the delegate variable, which returns an array of System.Delegate objects. I enumerate the contents of the array with a foreach loop and print out the value of the Target and Method properties for each Delegate object. (C# arrays are described in Chapter 13.) Table 10-2 describes the Target and Method properties.
Listing_07類創建了一個新的委托變量,並用它把Calculator和AlternateCalculator的方法組合起來。這里使用了委托變量的GetInvocationList方法,它返回一個System.Delegate對象的數組。用foreach循環枚舉了該數組的內容,並打印出每個Delegate對象的Target和Method屬性(C#數組在第13章描述)。表10-2描述了Target和Method屬性。

Table 10-2. The System.Delegate Properties
表10-2. System.Delegate屬性
Property
屬性
Description
描述
Target Returns the object that the delegate will use to invoke the method or null if the delegate method is static.
返回委托用來調用方法的對象(目標方法的父類 — 譯者注),若委托方法是static(靜態的),則返回null。
Method Returns a System.Reflection.MethodInfo that describes the method that will be invoked by the delegate.
返回一個System.Reflection.MethodInfo(方法信息),它描述由委托調用的方法(目標方法本身的信息 — 譯者注)。

Compiling and running the code in Listing 10-7 produces the following results:
編譯並運行清單10-7代碼將產生以下結果:

Target: Calculator
Method: Int32 CalculateSum(Int32, Int32)
Target: AlternateCalculator
Method: Int32 CalculateProduct(Int32, Int32)
Press enter to finish

從程序設計角度講,選擇性委托與質詢委托屬於兩個相反方向的委托處理。利用選擇性委托,我們可以根據某些條件有選擇地創建委托;而通過質詢委托,我們可以根據委托的一些信息有選擇地去做某些事情。 — 譯者注

Using Events
使用事件

Events are specialized delegates designed to simplify the callback model we saw earlier in the chapter. There can be a problem when you use a delegate type as a field, where one object interferes with another. Listing 10-8 contains a demonstration.
事件是設計用來簡化前述回調模型的特殊委托(這是事件的定義,即,事件是執行回調(或發送通知)的一種特殊形式的委托 — 譯者注)。在將委托用作為字段時,可能存在一個問題:一個對象會干擾另一個對象。清單10-8是一個演示。

Listing 10-8. One Type Modifying a Delegate Supplied by Another Type
清單10-8. 一個類型修改了由另一個類型提供的委托

using System; 
// 委托 delegate void NotifyCalculation(int x, int y, int result);
// 類,計算器 class Calculator { // 委托字段 public static NotifyCalculation CalculationPerformed;
// 方法,計算乘積 public static int CalculateProduct(int num1, int num2) { // perform the calculation // 執行計算,計算乘積 int result = num1 * num2;
// notify any listeners // 通知各個偵聽器 CalculationPerformed(num1, num2, result);
// return the result // 返回結果 return result; } }
// 類,一個邪惡的類 class NefariousClass { // 委托字段 private NotifyCalculation orig;
// 構造器 public NefariousClass() { // get a reference to the existing listener // 獲取對現有偵聽器的一個引用 orig = Calculator.CalculationPerformed;
// set a new listener for Calculator // 對Calculator設置一個新的偵聽器(將其指向了下面的那個扭曲的方法 — 譯者注) Calculator.CalculationPerformed = HandleNotifyCalculation; }
// 方法,一個扭曲的方法。在這個方法中做了兩件事: // (1)調用原委托謊報結果;(2)自己打印出正確結果 — 譯者注 public void HandleNotifyCalculation(int x, int y, int result) { // lie to the original listener // 謊報原偵聽器 // 把不良信息送入了原委托方法,這里送入的是加法結果 — 譯者注 orig(x, y, x + y);
// print out the details of the real calculation // 打印實際計算的細節 Console.WriteLine("NefariousClass: {0} x {1} = {2}", x, y, result); } }
class Listing_08 { static void Main(string[] args) { // set a listener for the Calculator class // 設置對Calculator類的一個偵聽器 Calculator.CalculationPerformed = StandardHandleResult;
// create an instance of the Nefarious class // 創建Nefarious類的一個實例 // 注意,在創建這個實例時,原偵聽器已經被修改, // 而指向了NefariousClass類的HandleNotifyCalculation方法 — 譯者注 NefariousClass nc = new NefariousClass();
// perform a calculation // 執行一個計算。 // 此時在CalculateProduct方法中執行委托回調時, // 實際執行的已經是HandleNotifyCalculation方法了 — 譯者注 Calculator.CalculateProduct(20, 72);
// wait for input before exiting // 退出之前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); }
private static void StandardHandleResult(int x, int y, int result) { Console.WriteLine("Good Class: {0} x {1} = {2}", x, y, result); } }

In this example, the Listing_08 class contains a method that matches the delegate type used for the Calculator.CalculationPerformed field. This method is used to process callbacks from the Calculator class. The idea is that the anonymous method will be called each time a calculation is performed by the Calculator class, just as in some of the earlier examples.
在這個例子中,Listing_08類含有一個用於Calculator.CalculationPerformed字段的委托類型相匹配的方法。該方法用於處理Calculator類的回調。其思想是Calculator類每執行一次計算,都調用這個方法(原文中說這是一個匿名方法,其實不是 — 譯者注),就像前面那些例子一樣。

The Listing_08 class also creates a new NefariousClass object, and the fun begins. The NefariousClass constructor assigns a new method to the Calculator delegate field, displacing the original. This method then feeds bad information to the original value of the delegate field. If we compile and run the code in Listing 10-9, we get the following results:
Listing_08類也創建了一個新的NefariousClass對象,於是開玩笑的事便開始了。NefariousrClass構造器把一個新方法賦給了Calcualtor的委托字段,替換了原有方法。然后這個方法(指已接管的方法HandleNotifyCalculation — 譯者注)把不良信息送入了原委托字段。如果編譯並運行清單10-9,會得到以下結果:

Good Class: 20 x 72 = 92
NefariousClass: 20 x 72 = 1440
Press enter to finish

The original method is invoked each time a calculation is performed, but NefariousClass has inserted itself in the way and changes the details of the calculation that is reported. And the problems don’t stop there—because the delegate field is public, any object can invoke the delegate as it wants, simulating callbacks even though no calculation has been performed.
每次執行一個計算時都會請求原方法,但NefariousClass阻擋了這一過程,並修改了所報告的計算細節。問題不僅於此 — 由於委托字段是public的,因此任何對象都可以在需要時請求這個委托,甚至在計算尚未執行時就模擬回調。

This example demonstrates deliberate interference, but most of the problems with public delegate fields arise because of sloppy programming, where an object makes an explicit assignment using = to set the value of the delegate field instead of using += to combine delegates. You could take steps to avoid this problem—make the delegate field private and implement methods that enforce checks to ensure that objects are not interfering with each other—but the C# event feature takes care of this for you. Defining an event is just like defining a delegate field, with the addition of the event keyword. So, here’s our delegate field from Listing 10-9:
這個示例演示了有意的干擾,但public委托字段的大多數問題都是由於草率編程所引發的(所以,在委托編程中,委托字段的訪問修飾是重要的 — 譯者注),在這些場合中,對象都是用 = 來設置委托字段的值,以形成一個明確的賦值,而不是用 += 來組合委托。可以采取一些步驟來避免這類問題 — 讓委托字段為private的,並實現強制檢查,以確保對象不會相互干擾 — 不過,C#的事件可以幫你照顧這些事。定義事件就像定義委托字段一樣,只要附加一個event關鍵字即可。因此,在以下是清單10-9的委托字段:

class Calculator {
    public static NotifyCalculation CalculationPerformed;

becomes the following:
把它改成下面這樣:

class Calculator {
    public static event NotifyCalculation CalculationPerformed;

注意,event關鍵字是附加在委托字段上的,因此,可以把事件看成是一種特殊形式的委托對象,是經過event修飾的一種委托對象,它讓委托對象的使用和操作受到一定的限制和約束。通過使用事件,便不會出現上述一個對象干擾另一個對象之類的問題 — 譯者注

When you make a delegate into an event, the class that contains the field can still invoke the delegate and make full use of the type members and operators. Every other object can use only the += and -= operators to add and subtract methods. It is no longer possible to interrogate the delegate or replace the methods assigned to the delegate as I did in Listing 10-8. Although you can make any delegate into an event, there is strong convention in C# to use a certain pattern for events known as the EventHandler pattern, which makes it easier for others to use your events in their code. I describe the pattern in the following sections.
當你把一個委托變成一個事件時,包含該字段的類仍然能夠調用委托,並充分利用類型成員和操作符。每一個其它對象都只能用 += 和 -= 操作符來添加和移去方法。這就不再能像清單10-8所做的那樣去質詢委托,或替換賦給委托的方法。雖然,你可以讓任何委托成為事件,但C#有強行的約定,它要求使用事件的特定模式,這稱為EventHandler模式,它使其他人更易於在他們的代碼中使用你的事件(可見,EventHandler模式是一種編寫事件的約定模式,該模式使大家能以統一的方式編寫和使用事件,便於事件編程,也便於事件互用 — 譯者注)。以下幾小節描述這個模式。

注:由於事件是用來實現委托回調的,為了更好地理解以下的事件編程模式,這里對委托回調作以下回顧和總結:

  1. 委托回調可以實現由一個對象(通知源)向外部發送通知的目的。
  2. 通知源需要有一個委托字段和一個發出通知的方法。委托字段用以接收外部方法的注入,形成一個委托實例。發出通知的方法通過執行委托實例,實現向外發送通知的目的。
  3. 在通知源的外部,需要有一個可以注入通知源的方法(接收通知的方法),以便把它注入到通知源形成委托實例,並在通知源發出通知時接收通知,而且在接收到通知時作相應的處理工作。接收通知的方法有時也叫偵聽器,意即在偵聽到(接收到)通知時,對通知做出響應。
  4. 在實現通知的編程中(主程序中),需要將接收通知的方法注入到通知源中,這稱為通知的訂閱。

如果把上述委托回調對應到事件場景,應當有以下環節:

  1. 事件是用來實現委托回調的。因此,事件的目的是為了從一個對象(事件源)向外發送事件通知,以便外部對象在接收到通知時進行事件處理。
  2. 事件源需要定義一個事件句柄(事件字段)和一個發送事件通知的方法。事件句柄用以接收事件偵聽器(處理事件的方法)的注入,以形成事件委托對象。發送事件通知的方法通過執行事件委托,向外部發送事件通知,就好像向外部通告:“某個事件已經發生啦”。
  3. 事件偵聽器是另一個實體中可以作為偵聽器注入到事件源中的事件處理方法,以便在接收到事件通知時,對所發生的事件做出響應。
  4. 在實現事件的編程中(主程序中),需要將偵聽器注入到事件源中,這通常稱為事件訂閱

由上可見,事件的本質與委托回調是一致的,但為了克服前述委托回調所具有的干擾,以及讓事件有統一的編程和使用方式,對事件的編程需采用特定的編程模式,這個模式稱為EventHandler事件編程模式。 — 譯者注

Defining and Publishing EventHandler Pattern Events
定義並發布EventHandler模式的事件

The first step in defining an event is to derive a class from the System.EventArgs class, which contains properties and fields to contain any custom data that you want to pass when you invoke the event delegate; the name of this class should end with EventArgs. Listing 10-9 contains an example for the calculation notification from earlier examples.
定義事件的第一步是從System.EventArgs類(事件參數類)派生一個類,這個類需要包含在調用事件委托時希望傳遞的所有自定義數據的各種字段和屬性,這個類的名稱應當以EventArgs結尾。清單10-9是針對前面例子中計算通知的一個示例。

Listing 10-9. A Custom EventArgs Implementation
清單10-9. 一個自定義EventArgs實現

class CalculationEventArgs : EventArgs {
    // 定義事件委托的所有參數字段。
    // 注意,這些字段都是private的 — 譯者注
    private int x, y, result; 
// 構造器 public CalculationEventArgs(int num1, int num2, int resultVal) { x = num1; y = num2; result = resultVal; }
// 以下是與字段對應的屬性。注意,這些屬性都只有getter塊,即都是只讀的 — 譯者注 public int X { get { return x; } }
public int Y { get { return y; } }
public int Result { get { return result; } } }

You can include any fields and properties that you need to express information about your event, but you should make sure that the fields cannot be modified. This is because the same EventArgs object will be passed to each of the subscribers of your event, and a poorly or maliciously coded recipient could change the information that subsequent listeners receive. The CalculationEventArgs class derives from EventArgs and defines fields for the details of our calculation and a set of read-only properties to provide access to the field values. You can get more information about properties in Chapter 8 and more information about fields in Chapter 7.
你可以包括需要表示事件信息的任何字段和屬性,但要確保字段不能被修改(即,是只讀的 — 譯者注)。這是因為,同樣的EventArgs對象(你所定義的這些字段和屬性 — 譯者注)將被傳遞給該事件的每個訂戶,無知或惡意的代碼接收者可能會修改隨后的偵聽器所接收的信息。這個CalculationEventArgs類派生於EventArgs,且為計算細節定義了一組字段和只讀屬性,以提供對字段值的訪問。你可以從第8章了解更多關於屬性、從第7章了解更多關於字段的信息。

以上表明,事件編程的第一步需要定義一個派生於System.EventArgs類的事件參數類。這個類的名稱按約定要以EventArgs結尾。在這個類中,應當為事件的所有參數定義相應的字段和屬性 — 譯者注

The next step is to define an event in your class. You don’t have to define a delegate for events (although as I showed earlier you certainly can), because you can use the generic EventHandler delegate, which is part of the System namespace. Listing 10-10 demonstrates the definition of an event.
下一步是在你的類(事件源類 — 譯者注)中定義一個事件。你不必為事件定義一個委托(雖然我在前面已表明這是可以的),因為你可以使用泛型的EventHandler委托,它位於System命名空間中。清單10-10是一個事件定義。

Listing 10-10. Defining an Event Using the Genetic EventHandler Delegate
清單10-10. 用泛型EventHandler委托定義一個事件

class Calculator {
    public event EventHandler<CalculationEventArgs> CalculationPerformedEvent;
    ...
}

To define the event using the generic EventHandler delegate, you use your custom EventArgs class as the type parameter, as shown in the listing. The convention is that the name of the event should end with the word Event. You can learn more about generic types and generic type parameters in Chapter 15.
為了用泛型EventHandler委托來定義事件,你要以自定義的EventArgs類作為其類型參數,如清單所示。其約定是,事件的名稱應當以單詞Event結尾。第15章可以了解更多關於泛型類型及泛型類型參數的內容。

請把這個EventHandler理解為事件句柄,它是一種泛型委托類型,是用來定義事件(字段)的。用這個事件句柄定義事件可以將委托的定義和創建事件委托字段這兩步工作合並成一步,即,此時不需要再定義事件的委托了。定義這個事件委托的作用是用它來接收外部注入的事件偵聽器(事件處理方法) — 譯者注

The convention dictates that you put the code to invoke your event delegate in a method whose name starts with On concatenated with the event name, less the word event. So, for example, since we have defined an event called CalculationPerformedEvent, the method would be called OnCalculationPerformed. This method should make a copy of the event to avoid a race condition (race conditions arise in parallel programming and are explained in later in this book) and ensure that the event has subscribers by ensuring that the event field is not null. Listing 10-11 shows the Calculator class updated to use the event pattern fully.
該約定指明,為了調用這個事件委托,你會把代碼放到一個方法中,該方法的名稱以On開頭,后跟去掉單詞event的事件名稱。舉例來說,由於我們定義了一個名稱為CalculationPerformedEvent的事件,該方法要叫做OnCalculationPerformed。這個方法將形成該事件的一份拷貝,以避免競爭條件(競爭條件會出現在並行編程中,本書的后面會加以解釋),並通過保證事件字段非空的辦法來確保該事件已有訂戶。清單10-11展示了經過修改的Calculator類,以完全使用這種事件模式。

Listing 10-11. Implementing the EventArgs Pattern
清單10-11. 實現EventArgs模式

class Calculator {
    // 定義事件
    public event EventHandler<CalculationEventArgs> CalculationPerformedEvent; 
public int CalculateProduct(int num1, int num2) { // perform the calculation // 執行計算 int result = num1 * num2;
// publish the event // 發布事件,意即執行事件委托,向外部發出事件通知 — 譯者注 OnCalculationPerformed(new CalculationEventArgs(num1, num2, result));
// return the result // 返回結果 return result; }
// 在發布事件時調用的方法 private void OnCalculationPerformed(CalculationEventArgs args) { // make a copy of the event // 形成事件的一份拷貝 EventHandler<CalculationEventArgs> handler = CalculationPerformedEvent;
// check to see we have subscribers // 查看事件訂戶 if (handler != null) { handler(this, args); } }
}

You can see that the CalculateProduct method creates a new instance of the CalculationEventArgs class and uses it to call the OnCalculationPerformed method, which then copies the event, checks to see that it isn’t null, and invokes it.
可以看出,CalculateProduct方法創建了CalculationEventArgs類的一個新實例,並用它來調用OnCalculationPerformed方法,該方法然后拷貝事件,檢查如果不為null,便調用它。

事件編程的第二步是創建事件源類,這需要做三件事:

  1. 用EventHandler定義事件委托。EventHandler的參數是事件編程第一步中創建的事件參數類。該事件委托的名稱按約定要以Event結尾。該事件委托的作用是用來接收外部注入的事件處理方法,以形成一個實例化的事件委托對象。
  2. 編寫發送事件通知的方法。在這個方法中,通過執行OnXXX方法實現向外發送通知的目的。按約定,OnXXX方法名中的XXX是上一步的事件名稱去掉單詞Event的部分。送給OnXXX方法的參數是事件參數類的一個實例。
  3. 編寫OnXXX方法。在這個方法中需形成事件的一份拷貝,並判斷該事件有無訂戶,若有則執行事件。

以上三步工作如上述清單代碼的黑體所示 — 譯者注

Subscribing to events is just like using a delegate, with the exception that the subscriber is limited to using the += and -= operators. Listing 10-12 shows a class that uses the events defined in the previous examples.
訂閱事件就像使用委托一樣,只是把訂閱者限制到只能使用 += 和 -= 操作符。清單10-12展示了使用前述示例所定義的事件的一個類。

Listing 10-12. Subscribing to Events
清單10-12. 訂閱事件

class Listing_12 {
    static void Main(string[] args) {
        // create a new instance of the Calculator class
        // 創建Calculator類的新實例
        Calculator calc = new Calculator();
// subscribe to the event in the Calculator class // 訂閱Calculator類中的事件(將一個外部方法注入到事件源 — 譯者注) calc.CalculationPerformedEvent += HandleEvent;
// perform a calculation // 執行計算 calc.CalculateProduct(20, 72);
// wait for input before exiting // 退出前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); }
// 處理事件方法 // 作為偵聽器可以被注入到事件源中去, // 並在接收到事件源發出的通知時,對該事件進行處理(響應) — 譯者注 static void HandleEvent(object sender, CalculationEventArgs e) { Console.WriteLine("Good Class: {0} x {1} = {2}", e.X, e.Y, e.Result); } }

由上可見,要實現事件的訂閱與退訂,需要做兩件事:

  1. 編寫事件處理方法:事件處理方法也叫偵聽器。事件處理方法是用來注入到事件源、以接收事件通知、並對通知做出響應的方法。注意,事件處理方法的參數有特殊的要求。
  2. 訂閱與退訂事件:在應用程序(主程序)中,將事件處理方法注入到事件源對象中(訂閱)。注意,只能以 += 或 -= 操作符進行事件的訂閱或退訂,否則會拋出異常。

事件處理方法的編程,以及事件的訂閱與退訂如上述清單的黑體所示 — 譯者注

You must ensure that the method you are going to use to handle events is not publically accessible; otherwise, you are still liable to encounter problems with other classes, as demonstrated by Listing 10-13.
必須確保打算用來處理事件的方法不是公開可訪問的(即,事件處理方法即上述說明中的事件偵聽器方法,該方法的訪問修飾符應當不是public的 — 譯者注),否則,與其它類一起使用時,仍會遇到問題,如清單10-13所示。

Listing 10-13. Removing Another Delegate from an Event
清單10-13. 從一個事件中移去另一個委托

using System; 
class CalculationEventArgs : EventArgs { private int x, y, result;
public CalculationEventArgs(int num1, int num2, int resultVal) { x = num1; y = num2; result = resultVal; }
public int X { get { return x; } }
public int Y { get { return y; } }
public int Result { get { return result; } } }
class Calculator { public event EventHandler<CalculationEventArgs> CalculationPerformedEvent;
public int CalculateProduct(int num1, int num2) { // perform the calculation // 執行計算 int result = num1 * num2;
// publish the event // 公布事件 OnCalculationPerformed(new CalculationEventArgs(num1, num2, result));
// return the result // 返回結果 return result; }
private void OnCalculationPerformed(CalculationEventArgs args) { // make a copy of the event // 形成事件的一份拷貝 EventHandler<CalculationEventArgs> handler = CalculationPerformedEvent;
// check to see we have subscribers // 檢查是否有訂戶 if (handler != null) { handler(this, args); } } }
class NefariousClass { public NefariousClass(Calculator calc) { // add a new listener for Calculator // 添加一個新的Calculator偵聽器 calc.CalculationPerformedEvent += HandleNotifyCalculation;
// unsubscribe someone else's event handler // 退訂其他人的事件處理器 calc.CalculationPerformedEvent -= Listing_13.HandleEvent; }
public void HandleNotifyCalculation(object sender, CalculationEventArgs e) { // print out the details of the real calculation // 打印出實際計算的細節 Console.WriteLine("NefariousClass: {0} x {1} = {2}", e.X, e.Y, e.Result); } }
class Listing_13 { static void Main(string[] args) { // create a new instance of the Calculator class // 創建Calculator類的新實例 Calculator calc = new Calculator();
// subscribe to the event in the calaculator class // 訂閱Calculator類中的事件 calc.CalculationPerformedEvent += HandleEvent;
// create an instance of NefariousClass // 創建NefariousClass實例 NefariousClass nef = new NefariousClass(calc);
// perform a calculation // 執行計算 calc.CalculateProduct(20, 72);
// wait for input before exiting // 退出前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); }
public static void HandleEvent(object sender, CalculationEventArgs e) { Console.WriteLine("Good Class: {0} x {1} = {2}", e.X, e.Y, e.Result); } }

The NefariousClass constructor in the example adds a new listener to the Calculator event, but it also removes the listener added by the Listing_13 class. It can do this because the Listing_13.HandleEvent method is public and therefore can be accessed outside of its containing class. Changing the access modifier on the HandleEvent class to a more restrictive setting would prevent this from happening.
上例中NefariousClass的構造器把一個新的偵聽器加到Calculator事件,但它也移去了Listing_13類加入的偵聽器。這確實是能夠做到的,因為Listing_13.HandleEvent方法是public的,因而可以在它的容器類的外部訪問它。把HandleEvent類的訪問修飾符改為更嚴格的設置將可以阻止這一情況的發生。

Creating Nongeneric Events
創建非泛型事件

Another approach to events is to use the nongeneric version of EventHandler. This is more like using a delegate in that you have to define the event/delegate type. This approach predates the introduction of generic types in C#, but I have included it because it is still widely used. Listing 10-14 shows the Calculator example implemented without generic support.
事件的另一種辦法是使用EventHandler的非泛型版本(這是事件的另一種編程模式 — 譯者注)。這更像以事件/委托類型的方式來使用委托。這種辦法在C#中要早於泛型類型的引入,我在這里包含此內容是因為它仍有廣泛的運用。清單10-14演示了不用泛型支持的Calculator示例實現。

Listing 10-14. Implementing Events Without Generic Types
清單10-14. 不用泛型類型實現事件

using System; 
delegate void CalculationPerformedEventHandler(object sender, CalculationEventArgs args);
class CalculationEventArgs : EventArgs { private int x, y, result;
public CalculationEventArgs(int num1, int num2, int resultVal) { x = num1; y = num2; result = resultVal; }
public int X { get { return x; } }
public int Y { get { return y; } }
public int Result { get { return result; } } }
class Calculator { public event CalculationPerformedEventHandler CalculationPerformedEvent;
public int CalculateProduct(int num1, int num2) { // perform the calculation // 執行計算 int result = num1 * num2;
// publish the event // 公布事件 OnCalculationPerformed(new CalculationEventArgs(num1, num2, result));
// return the result // 返回結果 return result; }
private void OnCalculationPerformed(CalculationEventArgs args) { // make a copy of the event // 形成事件拷貝 CalculationPerformedEventHandler handler = CalculationPerformedEvent;
// check to see we have subscribers // 檢查是否已有訂戶 if (handler != null) { handler(this, args); } } }
class Listing_14 { static void Main(string[] args) { // create a new instance of the Calculator class // 創建Calculator類的新實例 Calculator calc = new Calculator();
// subscribe to the event in the calaculator class // 訂閱Calculator類中的事件 calc.CalculationPerformedEvent += HandleEvent;
// perform a calculation // 執行計算 calc.CalculateProduct(20, 72);
// wait for input before exiting // 退出前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); }
static void HandleEvent(object sender, CalculationEventArgs e) { Console.WriteLine("Good Class: {0} x {1} = {2}", e.X, e.Y, e.Result); } }

There isn’t much to say about this example; it is very similar to the generic event listings but has an additional delegate definition.
這個例子無需多說,除了有一個附加的委托定義之外,它與前述泛型事件完全類似。

Creating Events Without Custom Data
創建無自定義數據的事件

If you don’t need to pass custom data as part of your event, then you can use EventArgs directly, as shown in Listing 10-15.
如果不需要把自定義數據作為事件的一部分進行傳遞,那么可以直接使用EventArgs,如果清單10-15所示。

Listing 10-15. Creating and Using Events with No Custom Data
清單10-15. 創建和使用無自定義數據的事件

using System; 
class Calculator { public event EventHandler CalculationPerformedEvent;
public int CalculateProduct(int num1, int num2) { // perform the calculation // 執行計算 int result = num1 * num2;
// publish the event // 公布事件 OnCalculationPerformed();
// return the result // 返回結果 return result; }
private void OnCalculationPerformed() { // make a copy of the event // 形成事件拷貝 EventHandler handler = CalculationPerformedEvent;
// check to see we have subscribers // 檢查是否已有訂戶 if (handler != null) { handler(this, EventArgs.Empty);
} } }
class Listing_15 { static void Main(string[] args) { // create a new instance of the Calculator class // 創建Calculator類新實例 Calculator calc = new Calculator();
// subscribe to the event in the calaculator class // 訂閱Calculator類中的事件 calc.CalculationPerformedEvent += HandleEvent;
// perform a calculation // 執行計算 calc.CalculateProduct(20, 72);
// wait for input before exiting // 退出之前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); }
static void HandleEvent(object sender, EventArgs e) { Console.WriteLine("Event Received"); } }

There is no need to define a custom delegate if you don’t need custom data. When defining the event, you simply use the EventHandler type, as follows:
如果不需要自定義數據,沒必要定義一個自定義委托。當定義事件時,你只要簡單地使用EventHandler類型,如下所示:

public event EventHandler CalculationPerformedEvent;

The event still requires two arguments to invoke it, but you can use the static EventArgs.Empty property to get a reference to a ready-made EventArgs instance that has no custom data. You can see this in the OnCalculationPerformed method of the Calculator class, which has been updated to remove the method parameters.
該事件仍然需要兩個參數去調用它,但你可以使用靜態的EventArgs.Empty屬性,以獲得對已形成的無自定義數據的EventArgs實例的引用。你可以在Calculator類的OnCalculationPerformed方法中看到這種情況,已對其作了修改,去除了方法參數。

由上可見,創建無自定義數據的事件,不需要創建事件參數類 — 譯者注

Applying Modifiers to Events
對事件運用修飾符

You can control access to your events using the public, protected, internal, and private keywords. See Chapter 7 for details of these keywords and the effect they have on fields.
你可以用public、protected、internal和private關鍵字來控制對事件的訪問。參閱第7章關於這些關鍵字的細節,以及它們運用於字段所具有的效應。

You should not use the virtual modifier on events. If you do, it is possible that your events will not be delivered properly. If you want to override an event from a base class, then you should mark the OnXXX method as virtual and override the method in the derived class. Listing 10-16 provides a demonstration. You can find more details and examples of overriding methods in Chapter 9.
你不應該在事件上使用virtual修飾符。如果這么做,你的事件可能不會被適當地投遞。如果你想重寫基類的事件,那么你應當將OnXXX方法標記為virtual,並在派生類中重寫該方法。清單101-6提供了一個演示。在第9章中你會看到重寫方法的更多細節和示例。

Listing 10-16. Deriving Event Implementations
清單10-16. 派生事件實現

// 基類
class Calculator {
    public event EventHandler<CalculationEventArgs> CalculationPerformedEvent; 
public int CalculateProduct(int num1, int num2) { // perform the calculation // 執行計算 int result = num1 * num2;
// publish the event // 公布事件 OnCalculationPerformed(new CalculationEventArgs(num1, num2, result));
// return the result // 返回結果 return result; }
protected virtual void OnCalculationPerformed(CalculationEventArgs args) { // make a copy of the event // 形成事件拷貝 EventHandler<CalculationEventArgs> handler = CalculationPerformedEvent;
// check to see we have subscribers // 確認已有訂戶 if (handler != null) { handler(this, args); } } }
// 派生類 class DerivedCalc : Calculator { protected override void OnCalculationPerformed(CalculationEventArgs args) { // perform custom logic here // 以下執行自定義邏輯 // call the base method // 調用基方法 base.OnCalculationPerformed(args); } }

In this example, the DerivedCalc class overrides the OnCalculationPerformed method, which has been marked as virtual in the base Calculator class. The overridden method can perform modifications to the custom EventArgs implementation (or perform any other required task) before calling the base OnCalculationPerformed method to publish the event.
在這個例子中,DerivedCalc類重寫了OnCalculationPerformed方法,它在基Calculator類中已被標記為virtual了。重寫的方法可以在調用基OnCalculationPerformed方法來公布事件之前,對自定義的EventArgs實現進行修改(或執行任何其它所需的任務)。

Using Action and Func Delegates
使用Action和Func委托

The Func and Action classes are special types that allow you to use delegates without having to specify a custom delegate type. They are used throughout the .NET Framework. For example, you will see examples of both when we look at parallel programming.
Func和Action類是特殊的類型,它們允許你在不必指定自定義委托類型的情況下,去使用委托。在整個.NET框架中都可以使用它們。例如,在我們考察並行計算時,你也會看到這兩個類的示例。

Func和Action是.NET的兩個特殊類型,在整個.NET平台中都可以使用它們。這兩個類型實際上是委托類型,可以用來創建委托對象。因此,由於它們是類型,我們便可以創建Func和Action類型的變量;又由於它們可以用來創建委托對象,於是直接用方法名對這些變量進行賦值,便可以形成實例化的委托對象,而不必進行委托定義 — 譯者注

Using Action Delegates
使用Action委托

Action delegates encapsulate methods that do not return results. In other words, they can be used only with methods that are defined with the void keyword. The simplest Action delegate is the System.Action class, which is used for methods that have no parameters (and no results). Listing 10-17 contains an example.
Action委托封裝不返回結果的方法(Action是可以用來委托無返回類型的方法,意即,可以用無返回類型的方法名對Action類型的變量進行賦值 — 譯者注)。換句話說,這種委托只能用於以void關鍵字定義的那些方法。最簡單的void委托是System.Action類,它用於無參數(且無結果)的方法。清單10-17含有一個例子。

Listing 10-17. Using the Basic Action Delegate
清單10-17. 使用基本的Action委托

using System; 
class Calculator { public void CalculateProduct() { // perform the calculation // 執行計算 int result = 10 * 20;
// print out a message with the result // 用結果印出消息 Console.WriteLine("Result: {0}", result); } }
class Listing_17 { static void Main(string[] args) { // create a new instance of Calculator // 創建Calculator新實例 Calculator calc = new Calculator();
// create an action and assign a method // 創建一個Action並賦予一個方法 // 以下語句創建了一個Action類型的變量,並用一個方法名對其實例化,形成了一個委托對象 — 譯者注 Action act = calc.CalculateProduct;
// invoke the method via the Action // 通過這個Action調用該方法 act();

// wait for input before exiting // 退出前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); } }

The key statement in Listing 10-17 is shown in bold; it creates a new local Action variable and assigns the CalculateProduct method from an instance of Calculator as the value. The Action is then invoked, just as a regular delegate would be. Compiling and running the code in Listing 10-17 products the following results:
清單10-17中的關鍵語句以黑體顯示,它創建了一個新的Action局部變量,並把Calculator實例的CalculateProduct方法作為值賦給了它。然后這個Action被調用,就像規則的委托那樣。編譯並運行清單10-17會產生以下結果:

Result: 200
Press enter to finish

We didn’t have to define a custom delegate in this example. The System.Action type handled everything for us. There are 17 different Action implementations available. Starting with the one used in Listing 10-17, each adds a new generic parameter. This is not as confusing as it may sound; you just create the generic implementation that matches the number of parameters the target method required. Listing 10-18 contains an example that uses two parameters.
在這個例子中,我們不必定義一個自定義委托(即,不必進行委托定義 — 譯者注)。System.Action類型為我們處理所有事情。有17個不同的Action實現可用。從清單10-17所用的一個開始,每一個都添加了一個新的泛型參數。實際情況並不像聽上去這樣讓人困擾,你只要創建與目標方法所需要的參數數目匹配的泛型實現。清單1-18包含了一個使用兩個參數的示例。

Listing 10-18. Using a Generic Action Delegate
清單10-18. 使用泛型的Action委托

using System; 
class Calculator { public void CalculateProduct(int x, int y) { // perform the calculation // 執行計算 int result = x * y;
// print out a message with the result // 用result打印出消息 Console.WriteLine("Result: {0}", result); } }
class Listing_18 { static void Main(string[] args) { // create a new instance of Calculator // 創建Calculator新實例 Calculator calc = new Calculator();
// create an action and assign a method // 創建一個action,並賦予一個方法 Action<int, int> act = calc.CalculateProduct;
// invoke the method via the Action // 通過Action調用該方法 act(10, 20);

// wait for input before exiting // 退出前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); } }

In this example, the method I want to delegate has two int parameters, so I used the Action<int, int> delegate (the parameter types for an Action need not all be the same). If I had wanted to delegate a method with five parameters, then I would have used the Action<T1, T2, T3, T4, T5> type and filled in the type parameters of the Action type to match the parameter types of the delegated method.
在這個例子中,我想委托的方法有兩個int參數,因此,我使用了Action<int, int>委托(Action的參數類型並不需要是相同的)。如果我想委托一個具有五個參數的方法,那么,我會使用Action<T1, T2, T3, T4, T5>類型,並填充這個Action類型的類型參數,以匹配被委托方法的參數類型。

Using Func Delegates
使用Func委托

System.Func delegates are just like Action delegates, except that they can return results. The simplest Func implementation has no parameters. Listing 10-19 contains an example; you can see the similarities to the Action examples.
System.Func委托除了可以返回結果以外,它與Action委托完全相同。最簡單的Func實現沒有參數。清單10-19包含一個示例,你可以看出它與Action示例的類似性。

Listing 10-19. A Simple Func Example
清單10-19. 一個簡單的Func示例

using System; 
class Calculator { public int CalculateProduct() { // perform the calculation // 執行計算 return 10 * 20; } }
class Listing_19 { static void Main(string[] args) { // create a new instance of Calculator // 創建Calculator新實例 Calculator calc = new Calculator();
// create a Func and assign a method // 創建一個Func,並賦予一個方法 Func<int> act = calc.CalculateProduct;
// invoke the method via the Action(應當是Func) // 通過Func調用方法 int result = act();

// print out the result // 印出結果 Console.WriteLine("Result: {0}", result); // wait for input before exiting // 退出前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); } }

The last generic type for System.Func is the result type for the delegate. So, in Listing 10-19, the Func<int> I used had no parameters but returned an int. Just like Action, there are 17 Func implementations with an increasing number of parameters, each of which can be of a different type. Listing 10-20 demonstrates using Func with two int parameters.
System.Func的最后一個泛型類型是委托的結果類型。因此,在清單10-19中,我所使用的Func<int>沒有參數,但返回int。就像Action一樣,也有17個帶有不同參數數目的Func實現,每個參數都可以是不同的類型。清單10-20演示了使用兩個int參數的Func。

Listing 10-20. Using a Func with Parameters
清單10-20. 使用帶有參數的Func

using System; 
class Calculator { public int CalculateProduct(int x, int y) { // perform the calculation // 執行計算 return x * y; } }
class Listing_20 { static void Main(string[] args) { // create a new instance of Calculator // 創建Calculation新實例 Calculator calc = new Calculator();
// create a Func and assign a method // 創建Func並賦予一個方法 Func<int, int, int> act = calc.CalculateProduct; // invoke the method via the Action Func // 通過這個Func調用該方法 int result = act(10, 20);
// print out the result // 印出結果 Console.WriteLine("Result: {0}", result);
// wait for input before exiting // 退出前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); } }

感謝微軟工程師,為我們提供了這么好用的Action和Func。有了它們,我們使用委托簡直太方便了 — 譯者注

Anonymous Methods
匿名方法

All of the examples so far in this chapter have used named methods, that is, methods that exist in classes and have a method identifier. C# also supports anonymous methods, which allow you to implement a delegate without defining a method. Listing 10-21 contains an anonymous method.
本章目前為止所演示的示例都使用了命名的方法,即,在類中存在且有方法修飾符的方法。C#也支持匿名方法,它讓你能夠不必定義方法就可以實現委托。清單10-21含有一個匿名方法。

Listing 10-21. An Anonymous Method
清單10-21. 匿名方法

using System; 
class Listing_21 { static void Main(string[] args) { // create a new Calculator // 創建一個新的Calculator Calculator calc = new Calculator();
// create a delegate with an anonymous method // 用匿名方法創建一個委托 calc.CalculationPerformedEvent += delegate(object sender, CalculationEventArgs e) { Console.WriteLine("Anonymous Calc: {0} x {1} = {2}", e.X, e.Y, e.Result); };
// perform a calculation // 執行計算 calc.CalculateProduct(20, 40);
// wait for input before exiting // 退出前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); } }

The class in Listing 10-21 works with the Calculator and CalculationEventArgs classes defining in Listing 10-9. I have omitted them from this listing for brevity. You can see the anonymous method in bold; it is also illustrated in Figure 10-2.
清單10-21中的類使用了清單10-9中定義的Calculator和CalculationEventArgs類。出於簡化,我忽略了它們。你可以看到以黑體表示的匿名方法,也如圖10-2所示。

IntrC10-2

圖中:Delegate Keyword: 委托關鍵字;Parameters:參數;Code Statements:代碼語句

Figure 10-2. The anatomy of an anonymous method
圖10-2. 一個匿名方法的剖析

An anonymous method has a number of similarities to a named method. You can see the parameters and code block in Figure 10-2 are just like those you would get in a regular method. There is no method identifier (because there is no method name), and you must use the delegate keyword when defining an anonymous method.
匿名方法與命名方法有許多相似性。你可以看出,圖10-2中的參數和代碼塊與你要在規則方法中得到的一樣。沒有方法修飾符(因為沒有方法名),而且,在定義匿名方法時,你必須使用delegate關鍵字。

Using an anonymous method as a delegate is just like using a named method, as you can see from Listing 10-21. I used the += operator to add the anonymous method to the event in the Calculator class.
將匿名方法用作為委托與使用命名方法一樣,正如你從清單10-21所看到的,我使用了+=操作符把匿名方法加到了Calculator類中的事件。

When the event invokes the delegate, the statements in the anonymous method body are executed, just as would happen for a regular method.
當事件調用委托時,匿名方法體中的語句被執行,就像規則方法所發生的情況一樣。

If we compile and run the code in Listing 10-21, we get the following results:
如果編譯並運行清單10-21代碼,會得到以下結果:

Anonymous Calc: 20 x 40 = 800
Press enter to finish

Anonymous methods work very well with Func and Action delegates, allowing you to define a delegate without needing to create a delegate type or implement a named method. When implementing a delegate that returns a result, simply use the return keyword as you would for a named method, as demonstrated by Listing 10-22.
匿名方法可以與Func和Action委托很好地一起使用,這允許你定義一個委托,而不需要創建委托類型或實現命名方法。當實現返回一個結果的委托時,簡單地使用return關鍵字,就像使用命名方法一樣,如清單10-22所示。

Listing 10-22. An Anonymous Method That Returns a Result
清單10-22. 返回一個結果的匿名方法

using System; 
class Listing_22 { static void Main(string[] args) { // define a func and implement it with an anonymous method // 定義一個func,並用一個匿名方法實現它 Func<int, int, int> productFunction = delegate(int x, int y) { return x * y; };
// invoke the func and get a result // 調用這個func,並得到一個結果 int result = productFunction(10, 20);
// print out the result // 印出結果 Console.WriteLine("Result: {0}", result);
// wait for input before exiting // 退出前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); } }

The bold statement in Listing 10-22 defines a Func that returns an int result and that has two int parameters. The implementation of this delegate is provided by an anonymous method that returns the product of the two parameters values. You invoke the delegate in the normal way, as though it were a method. Compiling and running the code in Listing 10-22 produces the following results:
清單10-22中的黑體語句定義了一個返回int結果且有兩個int參數的Func。這個委托的實現是由一個匿名方法實現的,它返回兩個參數值的積。用常規的方式調用這個委托,就像它是一個方法一樣。編譯並運行清單10-22代碼產生以下結果:

Result: 200
Press enter to finish

Capturing Outer Variables
捕捉外部變量

Anonymous methods do more than reduce the number of methods in your class; they are also able to access the local variables that are defined in the containing methods, known as outer variables. Listing 10-23 provides a demonstration.
匿名方法的作用還不止能減少類中的方法數,它們也能訪問容納方法(指容納該匿名方法的容器 — 譯者注)中定義的局部變量,這稱為外部變量(outer variables)。清單10-23提供了一個演示。

Listing 10-23. Accessing Outer Variables
清單10-23. 訪問外部變量

using System; 
class Calculator { Func<int, int, int> calcFunction;
public Calculator(Func<int, int, int> function) { calcFunction = function; }
public int PerformCalculation(int x, int y) { return calcFunction(x, y); } }
class Listing_23 { static void Main(string[] args) { // define a local variable // 定義一個局部變量 int calculationCount = 0;
// define and implement a Func // 定義並實現一個Func Func<int, int, int> productFunc = delegate(int x, int y) { // increment the outer variables // 遞增外部變量 calculationCount++;
// calculate and return the result // 計算並返回結果 return x * y; };
// create a new instance of Calculator // 創建Calculator新實例 Calculator calc = new Calculator(productFunc);
// perform several calculations // 執行一些計算 for (int i = 0; i < 5; i++) { Console.WriteLine("Result {0} = {1}", i, calc.PerformCalculation(i, i)); }
// print out the value of the outer variable // 印出外部變量的值 Console.WriteLine("calculationCount: {0}", calculationCount);
// wait for input before exiting // 退出前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); } }

The Calculator class in this example takes a Func<int, int, int> as a constructor parameter and invokes the delegate when the PerformCalculation method is called. The Listing_23 class defines a matching Func using an anonymous method. This is passed to the constructor of the Calculator instance.
這個例子中的Calculator類以Func<int, int, int>作為構造器參數,並在調用PerformCalculation方法時調用該委托。Listing_23類用一個匿名方法定義了一個匹配的Func。它被傳遞給Calculator實例的構造器。

The anonymous method increments the calculationCount variable each time that it is invoked, even though the variable is defined outside of the anonymous method and even though the Func is invoked by an entirely different method in an entirely different object. Compiling and running the code in Listing 10-23 produces the following result:
匿名方法每次被調用時都遞增了calculationCount變量,即使這個變量是在匿名方法的外部定義的,甚至Func也是由完全不同的對象中完全不同的方法來調用的。編譯並運行清單10-23代碼產生以下結果:

Result 0 = 0
Result 1 = 1
Result 2 = 4
Result 3 = 9
Result 4 = 16
calculationCount: 5
Press enter to finish

Variables that are accessed outside the anonymous method are called captured variables. The calculationCount variable in Listing 10-23 was captured by the anonymous method. An anonymous method that captures variables is called a closure.
匿名方法外部被訪問的變量稱為被捕捉captured)變量(因此,匿名方法體外部的那些變量稱為外部變量,在匿名方法體中所訪問的那個外部變量稱為被捕捉變量 — 譯者注)。清單10-23中的calculationCount變量是由匿名方法來捕捉的。一個要捕捉變量的匿名方法稱為是一個閉包closure)。

Captured variables are evaluated when the delegate is invoked, which means that you should take care when making assumptions about the value of a variable. Listing 10-24 provides a demonstration.
在委托被調用時,會求取被捕捉變量的值,這意味着,在假設一個變量的值時要小心。清單10-24提供了一個演示。

Listing 10-24. Evaluating Captured Variables
清單10-24. 求取被捕捉變量的值

using System; 
class Listing_24 { static void Main(string[] args) { // define a variable that will be captured // 定義被捕捉變量 string message = "Hello World";
// define an anonymous method that will capture // the local variables // 定義一個將捕捉這個局部變量的匿名方法 Action printMessage = delegate() { Console.WriteLine("Message: {0}", message); };
// modify one of the local vaiables // 修改局部變量 message = "Howdy!";
// invoke the delegate // 調用委托 printMessage();
// wait for input before exiting // 退出前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); } }

When the anonymous method is defined, the value of the message variable is Hello World. But defining an anonymous method doesn’t capture the variables it references—that doesn’t happen until the delegate is invoked, by which time the value of the message variable has changed. Compiling and running the code in Listing 10-24 produces the following result:
在定義匿名方法時,message變量的值是Hello World。但在定義匿名方法時並不捕捉它所引用的變量 — 直到委托被調用時才會發生,此時message變量的值已經變化了。編譯並運行清單10-24代碼產生以下結果:

Message: Howdy!
Press enter to finish

Lambda Expressions
Lambda表達式

Lambda expressions have largely replaced anonymous methods since they were introduced in C# 3.0. They have much the same functionality as an anonymous method but are slightly more convenient to use. Listing 10-25 contains an anonymous method and an equivalent lambda expression.
Lambda表達式已經廣泛代替了匿名方法,因為它是從C# 3.0開始引入的。Lambda表達式與匿名方法有很多同樣的功能,但更便於使用。清單10-25包含了一個匿名方法和一個等效的Lambda表達式。

Listing 10-25. Comparing an Anonymous Method with a Lambda Expression
清單10-25. 匿名方法與Lambda表達式比較

using System; 
class Listing_25 { static void Main(string[] args) { // implement an anonymous method that multiplies ints // 實現一個int數相乘的匿名方法 Func<int, int, int> anonFunc = delegate(int x, int y) { return x * y; };
// do the same thing with a lambda expression // lambda表達式做同樣的事情 Func<int, int, int> lambaFunc = (x, y) => { return x * y; };
// invoke the delegates // 調用委托 Console.WriteLine("Anonymous Method Result: {0}", anonFunc(10, 10)); Console.WriteLine("Lambda Expression Result: {0}", lambaFunc(10, 10));
// wait for input before exiting // 退出前等待輸入 Console.WriteLine("Press enter to finish"); Console.ReadLine(); } }

The lambda expression in Listing 10-25 is shown in bold. There are only three parts to a lambda expression, and they are illustrated in Figure 10-3.
清單10-25中的lambda表達式以黑體顯示。一個lambda表達式只有三個部分,它們如圖10-3所示。

IntrC10-3

圖中:Parameters:參數,Lambda Operator:Lambda操作符,Code Statements:代碼語句

Figure 10-3. The anatomy of a lambda expression
圖10-3. Lambda表達式剖析

Although the format of a lambda expression looks a little odd, the basic premise is the same as for anonymous methods. Parameters in a lambda expression are specified without their types—the types are inferred.
雖然lambda表達式看上去有點古怪,但基本前提與匿名方法是相同的。Lambda表達式的參數不指定類型 — 其類型通過推斷。

The code statements are just like for a method, named or anonymous. You access the parameters by the names they have been specified by. In the case of Listing 10-25, the parameters x and y are multiplied together, and the result is returned.
其代碼語句與命名或匿名方法同樣。通過被指定的參數名稱來訪問參數。在清單10-25的示例中,參數x和y相乘,並返回結果。

You can omit the braces and the return keyword if there is a single operation in a lambda expression so that the lambda expression in Listing 10-25 can also be written as follows:
如果在lambda表達式中只有一個單一的操作,你可以忽略花括號和return關鍵字,因此,清單10-25的lambda表達式也可以寫成如下形式:

(x, y) => x * y;

The lambda operator (=>) is, as you might expect, required for lambda operators. It is often described as the goes to operator. For the expression in Listing 10-25, we can say that the parameters x and y go to the product of x and y.
lambda操作符(=>),正如你可能想到的,是必需的。通常把它說成是“進入go to)”操作符(感覺把 => 操作符說成“送入”更恰當些,意即將參數送入方法體 — 譯者注)。對於清單10-25中的表達式,我們可以說成,x和y參數進入x和y的乘積。

Compiling and running the code in Listing 10-25 produces the following results:
編譯並運行清單10-25代碼產生以下結果:

Anonymous Method Result: 100
Lambda Expression Result: 100
Press enter to finish

The C# compiler is pretty good at inferring the types of parameters for lambda expressions, but on occasion the compiler won’t be able to work it out, and you will you need to explicitly tell the compiler what they are. Here is an example:
C#編譯器十分擅長推斷lambda表達式的參數類型,但偶爾也會有編譯器不能推斷的情況,此時你需要明確地告訴編譯器,它們是什么。以下是一個示例:

(int x, int y) => x * y;

I have used lambda expressions extensively in software projects and for other C# books, and I have found only a small number of instances where the compiler couldn’t figure things out implicitly, but when you do encounter one of those situations (or if you just prefer explicit typing), then it is good to know how to do it.
我在軟件項目以及其它C#書籍中廣泛使用了lambda表達式。而且我發現,只有很少情況下才會出現編譯器不能推斷的情況。當你遇到這種情況時(或者你更喜歡明確指定類型),那么,最好知道如何做這種事。

Summary
小結

In this chapter, you have how delegates can be used to encapsulate references to static and instance methods and passed around like regular types. You have seen the limitations of delegates when they are shared between objects and how events address this shortcoming.
在本章中,你知道了如何把委托用於封裝對靜態和實例化方法的引用,並像規則類型一樣進行傳遞。你也看到了,對象之間共享委托所具有的局限性,以及事件是如何解決這一缺陷的。

The convention for using events is widely adopted, and I recommend that you follow it in your own code. Not only does it make your code easier for other programmers to use, but it also helps you create events that can be overridden reliably in derived classes.
使用事件的約定被廣泛采納,而且我建議你在代碼中遵循它。不僅它使你的代碼更易於被其他程序員使用,而且也有助於你創建能夠在派生類中可靠地進行重寫的事件。

We looked briefly at the System.Func and System.Action types, both of which allow us to work with delegates without creating custom delegate types, and we looked at anonymous methods and lambda expressions, which allow us to implement delegates without having to define methods. We learned how anonymous methods and lambda expressions capture local variables and when those variables are evaluated. We’ll see a lot more of Func, Action, and lambda expressions when we explore some of the advanced C# language features such as LINQ and parallel programming.
我們簡要地考查了System.Func和System.Action類型,它們都能讓我們不必創建自定義委托類型就可以使用委托。我們也考查了匿名方法和lambda表達式,它們讓我們能夠不必定義方法就可以實現委托。我們了解了匿名方法和lambda表達式如何捕捉局部變量,以及評估這些變量的時間。在我們考察C#的高級語言特性,如LINQ和並行編程時,會看到更多Func、Action和lambda表達式。

這是一篇有技術含量、甚至可以說是有學術價值的文章!請大家多給推薦


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM