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提供了本章概要。
以上这两段文字告诉我们:委托是一种封装方法的特殊类型。事件是特殊形式的委托,用以实现对相关部件的通知。Func和Action是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所示。
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,这就像调用一个常规方法所发生的情况一样。(可见,调用委托就像执行常规的方法一样 — 译者注)
总结上述委托编程过程,可以从概念上形成以下几个名词:
- 委托定义:委托定义的作用是创建一个能够封装一类方法的类型。
- 委托字段:在委托编程中需要有一个委托字段,该字段可以用来创建委托对象。也可以把这个委托字段看成为是一个委托类型的变量(简称为委托变量)。于是,随时可以用一个方法对这个委托变量进行赋值。
- 委托实例:这是对委托字段的实例化,以形成委托对象。实例化的办法是用一个具有相同签名的方法名对委托变量进行赋值。实例化的作用是把委托字段与实例化方法关联在一起,形成委托对象。因此,无论何时,调用委托就是执行其实例化方法。
- 委托属性:可以用委托属性对外暴露委托对象。
- 执行/调用委托:执行或调用委托实际上是执行委托对象的实例化方法,执行/调用委托与执行规则的方法一样(送入方法参数、接收返回结果)。 — 译者注
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属性。
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模式是一种编写事件的约定模式,该模式使大家能以统一的方式编写和使用事件,便于事件编程,也便于事件互用 — 译者注)。以下几小节描述这个模式。
注:由于事件是用来实现委托回调的,为了更好地理解以下的事件编程模式,这里对委托回调作以下回顾和总结:
- 委托回调可以实现由一个对象(通知源)向外部发送通知的目的。
- 通知源需要有一个委托字段和一个发出通知的方法。委托字段用以接收外部方法的注入,形成一个委托实例。发出通知的方法通过执行委托实例,实现向外发送通知的目的。
- 在通知源的外部,需要有一个可以注入通知源的方法(接收通知的方法),以便把它注入到通知源形成委托实例,并在通知源发出通知时接收通知,而且在接收到通知时作相应的处理工作。接收通知的方法有时也叫侦听器,意即在侦听到(接收到)通知时,对通知做出响应。
- 在实现通知的编程中(主程序中),需要将接收通知的方法注入到通知源中,这称为通知的订阅。
如果把上述委托回调对应到事件场景,应当有以下环节:
- 事件是用来实现委托回调的。因此,事件的目的是为了从一个对象(事件源)向外发送事件通知,以便外部对象在接收到通知时进行事件处理。
- 事件源需要定义一个事件句柄(事件字段)和一个发送事件通知的方法。事件句柄用以接收事件侦听器(处理事件的方法)的注入,以形成事件委托对象。发送事件通知的方法通过执行事件委托,向外部发送事件通知,就好像向外部通告:“某个事件已经发生啦”。
- 事件侦听器是另一个实体中可以作为侦听器注入到事件源中的事件处理方法,以便在接收到事件通知时,对所发生的事件做出响应。
- 在实现事件的编程中(主程序中),需要将侦听器注入到事件源中,这通常称为事件订阅。
由上可见,事件的本质与委托回调是一致的,但为了克服前述委托回调所具有的干扰,以及让事件有统一的编程和使用方式,对事件的编程需采用特定的编程模式,这个模式称为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,便调用它。
事件编程的第二步是创建事件源类,这需要做三件事:
- 用EventHandler定义事件委托。EventHandler的参数是事件编程第一步中创建的事件参数类。该事件委托的名称按约定要以Event结尾。该事件委托的作用是用来接收外部注入的事件处理方法,以形成一个实例化的事件委托对象。
- 编写发送事件通知的方法。在这个方法中,通过执行OnXXX方法实现向外发送通知的目的。按约定,OnXXX方法名中的XXX是上一步的事件名称去掉单词Event的部分。送给OnXXX方法的参数是事件参数类的一个实例。
- 编写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); } }
由上可见,要实现事件的订阅与退订,需要做两件事:
- 编写事件处理方法:事件处理方法也叫侦听器。事件处理方法是用来注入到事件源、以接收事件通知、并对通知做出响应的方法。注意,事件处理方法的参数有特殊的要求。
- 订阅与退订事件:在应用程序(主程序)中,将事件处理方法注入到事件源对象中(订阅)。注意,只能以 += 或 -= 操作符进行事件的订阅或退订,否则会抛出异常。
事件处理方法的编程,以及事件的订阅与退订如上述清单的黑体所示 — 译者注
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 theActionFunc // 通过这个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所示。
图中: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所示。
图中: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表达式。