委托、Lambda表达式和事件

1. 引用方法
    委托是寻址方法的.NET版本。在C++中,函数指针只不过是一个指向内存位置的指针,它不是类型安全的。我们无法判断这个指针实际指向什么,像参数和返回类型等项就更无从知晓了。而.NET委托完全不同,委托是类型安全的类,它定义了返回类型和参数的类型。委托类不仅包含对方法的引用,也可以包含对多个方法的引用。
    Lambda表达式与委托类型直接相关。当参数时委托时,就可以使用Lambda表达式实现委托引用的方法。
2. 委托
    当要把方法传递给其他方法时,需要使用委托。我们习惯于把数据作为参数传递给方法,而有时某个方法执行的操作并不是针对数据进行的,而是要对另一个方法进行操作。更麻烦的是,在编译时我们不知道第二个方法是什么,这个信息只能在运行时得到。所以需要把第二个方法作为参数传递给第一个方法。这听起来很令人疑惑,下面用几个例子来说明:
  • 启动线程和任务——在C#线程的一个基类System.Threading.Thread的一个实例上使用方法Start(),就可以启动一个线程。如果要告诉计算机启动一个新的执行序列,就必须说明要在哪里启动该序列。必须为计算机提供开始启动的方法的细节,即Thread类的构造函数必须带有一个参数,该参数定义了线程调用的方法。
  • 通用库类——比如Sort(List<T> list,Func<T, T, bool> comparison)函数实现快速排序,则需要指定一个方法参数comparison,告诉排序函数如何实现对两个参数的比较。
  • 事件——一般是通知代码发生了什么事件。GUI编程主要处理事件。在引发事件时,运行库需要知道应执行哪个方法。这就需要把处理事件的方法作为一个参数传递给委托。
    在C和C++中,只能提取函数的地址,并作为一个参数传递它。C没有类型安全性。可以把任何函数传递给需要函数指针的方法。但是,这种直接方法不仅会导致一些关于类型安全性的问题,而且没有意识到:在进行面向对象编程时,几乎没有方法是孤立存在的,而是在调用方法前通常需要与类实例相关联。所以.NET Framework在语法上不允许使用这种直接方法。如果要传递方法,就必须把方法的细节封装在一种新类型的对象中,即委托。委托只是一种特殊类型的对象,其特殊之处在于,我们以前定义的对象都包含数据,而委托包含的只是一个或多个方法的地址。
2.1声明委托
    使用委托时,首先需要定义要使用的委托,对于委托,定义它就是告诉编译器这种类型的委托表示哪种类型的方法。然后,必须创建该委托的一个或多个实例。编译器在后台将创建表示该委托的一个类。
    定义为托的语法如下:
    delegate void IntMethodInvoker(int x);
    在这个示例中,定义了一个委托IntMethodInvoker,并指定该委托的每个实例都可以包含一个方法的引用,该方法带有一个int参数,并返回void。理解委托的一个要点是它们的类型安全性非常高。在定义委托时,必须给出它所表示的方法的签名和返回类型等全部细节(理解委托的一种好方式是把委托当做这样一件事情:它给方法的签名和返回类型指定名称)
    假定要定义一个委托TwoLongsOp,该委托表示的方法有两个long型参数,返回类型为double,可以编写如下代码:
    delegate double TwoLongsOp(long first, long second);
    或者要定义一个委托,它表示的方法不带参数,返回一个string型的值,可以编写如下代码:
    delegate string GetAString();
    其语法类似于方法的定义,但没有方法体,定义的前面要加上关键字delegate。因为定义委托基本上是定义一个新类,所以可以在定义类的任何相同地方定义委托,也就是说,可以在另一个类的内部定义,也可以在任何类的外部定义,还可以在命名空间中把委托定义为顶层对象。根据定义的可见性。和委托的作用域,可以在委托的定义上应用任意常见的访问修饰符:public、private、protected等。
    实际上,“定义一个委托”是指“定义一个新类”。委托实现为派生自基类System.MulticastDelegate的类,System.MulticastDelegate又派生自其基类System.Delegate。C#编译器能识别这个类,会使用其委托语法,因此我们不需要了解这个类的具体执行情况。这是C#与基类共同合作,是编程更易完成的另一个范例。
    定义好委托后,就可以创建它的一个实例,从而用它存储特定方法的细节。
    但是,在术语方面有一个问题。类有两个不同的术语:“类”表示比较广义的定义,“对象”表示类的实例。但委托只有一个术语。在创建委托的实例时,所创建的委托的实例仍成为委托。必须从上下文中确定委托的确切含义。
2.2  使用委托
    下面的代码说明了如何使用委托。这是在int上调用ToString()方法的一种相当冗长的方式:
private delegate string GetAString();
static void Main(string[] args)
{
    int x = 40;
    GetAString firstStringMethod = new GetAString(x.ToString);
    Console.WriteLine("string is {0}", firstStringMethod());
    //with firstStringMethod initialized to x.ToString(),
    //the above statement is equivalent to saying
    //Console.WriteLine("string is {0}",x.ToString());
}
    在这段代码中,实例化了类型为GetAString的一个委托,并对它进行初始化,使用它引用整型变量x的ToString()方法。在C#中,委托在语法上总是接受一个参数的构造函数,这个参数就是委托引用的方法。这个方法必须匹配最初定义委托时的签名。
    为了减少输入量,只要需要委托实例,就可以只传送地址的名称。这称为委托推断。只要编译器可以把委托实例解析为特定的类型,这个C#特性就是有效的。下面两个语句是等效的。
GetAString firstStringMethod = new GetAString(x.ToString);
GetAString firstStringMethod = x.ToString;
    C#编译器创建的代码是一样的。
    委托推断可以在需要委托实例的任何地方使用。委托推断也可以用于事件,因为事件是基于委托的。
    委托的一个特性是它们的类型是安全的,可以确保被调用的方法的签名是正确的。但是有趣的是。它们不关心在什么类型的对象上调用该方法,甚至不考虑该方法是静态方法,还是实例方法。
    给定委托的实例可以引用任何类型的任何对象上的实例方法或静态方法——只要方法的签名匹配于委托的签名即可。
2.3 Action<T>和Func<T>委托
    除了为每个参数和返回类型定义一个新类型委托类型之外,还可以使用Action<T>和Func<T>委托。泛型Action<T>委托表示引用一个void返回类型的方法。这个委托类存在不同的变体,可以传递至多16种不同的参数类型。没有泛型参数的Action类可调用没有参数的方法。Action<in T>调用带一个参数的方法,Action<in T2, in T2>调用带两个参数的方法,以此类推。
    Func<T>委托可以以类似的方式使用。Func<T>允许调用带返回类型的方法。与Action<T>类似,Func<T>也定义了不同的变体,至多也可以传递16个参数类型和一个返回值类型。Func<out TResult>委托类型可以调用带返回类型且无参数的方法,Func<in T, out TResult>调用带一个参数的方法,以此类推。
2.4 多播委托
    前面使用的每个委托都只包含一个方法调用。调用委托的次数与调用方法的次数相同。如果要调用多个方法,就需要多次显式调用这个委托。但是,委托也可以包含多个方法。这种委托成为多播委托。如果调用多播委托,就可以按顺序连续调用多个方法。为此,委托的签名就必须返回void;否则,就只能得到委托调用后最后一个方法的结果。
    如果正在使用多播委托,就应知道对同一个委托调用方法链的顺序并未正式定义。因此应避免编写依赖于特定顺序调用方法的代码。
    通过一个委托调用多个方法还可能导致一个大问题。多播委托包含一个逐个调用的委托集合,如果通过委托调用的其中一个方法抛出一个异常,整个迭代就会停止。在这种情况下,为了避免这个问题,应自己迭代方法列表。Delegate类定义GetInvocationList()方法,它返回一个Delegate对象数组。现在可以使用这个委托调用与委托直接相关的方法,捕获异常,并继续下一次迭代。
    Delegate[] delegates = firstStringMethod.GetInvocationList();
    foreach (Action d in delegates)
    {
        try
        {
            d();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
2.5 匿名方法
    到目前为止,要想使委托工作,方法必须已经存在(即委托是用它将调用的方法的相同签名定义的)。但还有另外一种使用委托的方式:即通过匿名方法。匿名方法是用作委托的参数的一段代码。用匿名方法定义委托的语法与前面的定义并没有区别。但在实例化委托时,就有区别了。
    string mid=", middle part,";
    Func<string, string> anonDel = delegate(string param)
    {
        param += mid;
        param += " and this was added to the string.";
        return param;
    };
    Console.WriteLine(anonDel("Start of string"));
    匿名方法的优点是减少了要编写的代码。不必定义仅由委托使用的方法。在为事件定义委托时,这是非常显然的。这有助于降低代码的复杂性,尤其是定义了好几个事件时,代码会显得比较简单。使用匿名方法时,代码执行速度并没有加快。编译器仍定义了一个方法,该方法只有一个自动指定的名称,我们不需要知道这个名称。
    使用匿名方法时必须遵循两个规则。在匿名方法中不能使用跳转语句(break、goto或continue)跳到该匿名方法的外部,反之亦然:匿名方法外部的跳转语句也不能跳到该匿名方法的内部。
    在匿名方法内部不能访问不安全的代码。另外,也不能访问在匿名方法外部使用的ref和out参数。但可以使用在匿名方法外部定义的其他变量。
    如果需要用匿名方法多次编写同一个功能,就不要使用匿名方法。此时与复制代码相比,编写一个命名方法比较好。从C#3.0开始,可以使用Lambda表达式替代匿名方法。有关匿名方法和Lambda表达式的区别,参考本分类下的《匿名方法和Lambda表达式
3. Lambda表达式
    自从C#3.0开始,就可以使用一种新语法把实现代码赋予委托:Lambda表达式。只要有委托参数类型的地方,就可以使用Lambda表达式。前面使用匿名方法的例子可以改为使用Lambda表达式:
    string mid=", middle part,";
    Func<string, string> lambda= param=>
    {
        param += mid;
        param += " and this was added to the string.";
        return param;
    };
    Console.WriteLine(lambda("Start of string"));
    Lambda表达式运算符“=>”的左边列出了需要的参数。Lambda运算符的右边定义了赋予lambda变量的方法的实现代码。
3.1 参数
    Lambda表达式有几种定义参数的方式。如果只有一个参数,只写出参数名就可以了,例如上面所示的代码。如果委托使用多个参数,就把参数名放在花括号中。例如:
    Func<double, double, double> twoParams = (x, y) => x * y;
    Console.WriteLine(twoParams(3, 2));
    为了方便,可以在花括号中给变量名添加参数类型。如果编译器不能匹配重载后的版本,那么使用参数类型可以帮助找到匹配的委托:
  Func<double, double, double> twoParams = (double x, double y)=> x * y;
  Console.WriteLine(twoParams(3, 2));
3.2 多行代码
    如果Lambda表达式只有一条语句,在方法块内就不需要花括号和return语句,因为编译器会添加一条隐式的return语句。但是如果在Lambda表达式的实现代码中需要多条语句,就必须添加花括号和return语句。例如本节开始的代码。
3.3 闭包
    通过Lambda表达式可以访问表达式外部的变量,这成为闭包。闭包是一个非常好的功能,但如果未正确使用,也会非常危险。特别是,通过另一个线程调用拉姆达表达式时,当前局部变量的值是不确定的。
    int someValue=5;
    Func<int, int> f = x=> x + someValue;
    对于Lambda表达式x=>x+someValue,编译器会创建一个匿名类,它有一个构造函数来传递外部变量。该构造函数取决于从外部传递进来的变量个数。对于这个简单的例子,构造函数接受一个int。匿名类包含一个匿名方法,其实现代码、参数和返回类型由lambda表达式定义:
    public class AnonymousClass
    {
        private int someValue;
        public AnonymousClass(int someValue)
        {
            this.someValue = someValue;
        }
        public int AnonymousMethod(int x)
        {
            return x + someValue;
        }
    }
    使用Lambda表达式并调用该方法,会创建匿名类的一个实例,并传递调用该方法时变量的值。
3.4 使用foreach语句的闭包
    针对闭包,C#5.0的foreach语句有了一个很大的改变。
 1 var values = new List<int>() {10,20,30 };
 2 var funcs = new List<Func<int>>();
 3 
 4 foreach (int val in values)
 5 {
 6     funcs.Add(() => val);
 7 }
 8 
 9 foreach (var f in funcs)
10 {
11     Console.WriteLine(f());
12 }
    在C#5.0中,这段代码的执行结果发生了变化。使用C#4或更早版本的编译器时,会在控制台中输出3次30.在第一个foreach循环中使用闭包时,所创建的函数是在调用时,而不是迭代时获得val变量的值。编译器会从foreach语句创建一个while循环。在C#4中,编译器在while循环外部定义循环变量,在每次迭代中重用这个变量。因此,在循环结束时,该变量的值就是最后一次迭代的值。要想在使用C#4时让代码的结果为10、20、30,必须将代码改为使用一个局部变量,并将这个局部变量传入Lambda表达式。这样,每次迭代时就将保留一个不同的值。
 1 var values = new List<int>() {10,20,30 };
 2 var funcs = new List<Func<int>>();
 3 
 4 foreach (int val in values)
 5 {
 6     int v = val;
 7     funcs.Add(() => v);
 8 }
 9 
10 foreach (var f in funcs)
11 {
12     Console.WriteLine(f());
13 }
    在C#5.0中,不再需要做这种代码修改。C#5.0会在while循环的代码块中创建一个不同的局部循环变量,所以值会自动得到保留。这是C#4与C#5.0的区别,必须知道这一点。
    Lambda表达式可以用于类型为委托的任意地方。类型是Expression或Expression<T>时,也可以使用Lambda表达式,此时编译器会创建一个表达式树。
4. 事件
    事件基于委托,为委托提供了一种发布/订阅机制。在架构内到处都能看到事件。在Windows应用程序中,Button类提供了Click事件。这类事件就是委托。触发Click事件时调用的处理程序方法需要定义,其参数由委托类型定义。
    在本节的示例代码中,事件用于连接CarDealer类和Consumer类。CarDealer类提供了一个新车到达时触发的事件。Consumer类订阅该事件,以获得新车到达的通知。
4.1 事件发布程序
    从CarDealer类开始,它基于事件提供一个订阅。CarDealer类用event关键字定义了类型为EventHandler<CarInfoEventArgs>的NewCarInfo事件。在NewCar()方法中,通过调用RaiseNewCarInfo方法触发NewCarInfo事件。这个方法的实现检查委托是否为空,如果不为空,就引发事件。
 1     public class CarInfoEventArgs : EventArgs
 2     {
 3         public CarInfoEventArgs(string car)
 4         {
 5             this.Car = car;
 6         }
 7         public string Car { get; private set; }
 8     }
 9 
10     public class CarDealer
11     {
12         public event EventHandler<CarInfoEventArgs> NewCarInfo;
13         public void NewCar(string car)
14         {
15             Console.WriteLine("CarDealer, new car {0}.", car);
16             RaiseNewCarInfo(car);
17         }
18         protected virtual void RaiseNewCarInfo(string car)
19         {
20             EventHandler<CarInfoEventArgs> newCarInfo = NewCarInfo;
21             if (newCarInfo != null)
22                 newCarInfo(this, new CarInfoEventArgs(car));
23         }
24     }
    CarDealer类提供了EventHandler<CarInfoEventArgs>类型的NewCarInfo事件。作为一个约定,事件一般使用带两个参数的方法,其中第一个参数是一个对象,包含事件的发送者。第二个参数提供了事件的相关信息。第二个参数随不同的事件类型而不同。.NET1.0为所有不同数据类型的事件定义了几百个委托,有了泛型委托EventHandler<T>之后,就不再需要委托了。EventHandler<TEventArgs>定义了一个处理程序,它返回void,接受两个参数。对于EventHandler<TEventArgs>,第一个参数必须是object类型,第二个参数是T类型。EventHandler<TEventArgs>还定义了一个关于T的约束:它必须派生自基类EventArgs。CarInfoEventArgs就派生自基类EventArgs。
    委托EventHandler<TEventArgs>的定义如下:
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)
    where TEventArgs:EventArgs;
    在一行上定义事件是C#的简化记法。编译器会创建一个EventHandler<CarInfoEventArgs>委托类型的变量,以便从委托中订阅和取消订阅。该简化记法的较长形式如下所示。这非常类似于自动属性和完整属性之间的关系。对于事件,使用add和remove关键字添加和删除委托的处理程序:
private EventHandler<CarInfoEventArgs> newCarInfo;//原文此处有误:private delegate EventHandler<CarInfoEventArgs> newCarInfo;不能通过编译(C#高级编程第八版)
public event EventHandler<CarInfoEventArgs> NewCarInfo
{
    add { newCarInfo += value; }
    remove { newCarInfo -= value; }
}
    如果不仅仅需要添加和删除事件处理程序,定义事件的长记法就很有用。例如,需要为多个线程访问添加同步操作。WPF控件使用长记法给事件添加冒泡和隧道功能。
    CarDealer类在RaiseNewCarInfo方法中触发事件。使用NewCarInfo和花括号可以调用给定事件订阅的所有处理程序。注意与多播委托一样,方法的调用顺序无法保证。为了更多地控制处理程序的调用,可以使用Delegate类的GetInvocationList方法访问委托列表中的每一项,并独立地调用每个方法。在触发事件之前,需要检查委托NewCarInfo是否不为空。如果没有订阅处理程序,委托就是空。
4.2 事件侦听器
    Consumer类用作事件侦听器。这个类订阅了CarDealer类的事件,并定义了NewCarIsHere方法,该方法满足EventHandler<CarInfoEventArgs>委托的要求,其参数类型是object和CarInfoEventArgs:
public class Consumer
{
    private string name;

    public Consumer(string name) { this.name = name; }

    public void NewCarIsHere(object sender, CarInfoEventArgs e)
    {
        Console.WriteLine("{0} : Car {1} is new.", name, e.Car);
    }
}
    现在需要连接事件发布程序和订阅器。为此使用CarDealer类的NewCarInfo事件,通过“+=”创建一个订阅。消费者micheal(变量)订阅了事件,接着消费者sebastian(变量)也订阅了事件,然后michael通过-=取消了订阅。
 1 static void Main(string[] args)
 2 {
 3     var dealer = new CarDealer();
 4     var michael = new Consumer("Michael");
 5     dealer.NewCarInfo += michael.NewCarIsHere;
 6 
 7     dealer.NewCar("Ferrari");
 8 
 9     var sebastian = new Consumer("Sebastian");
10     dealer.NewCarInfo += sebastian.NewCarIsHere;
11 
12     dealer.NewCar("Mercedes");
13 
14     dealer.NewCarInfo -= michael.NewCarIsHere;
15 
16     dealer.NewCar("Red Bull Racing");
17 }
    运行应用程序,一辆Ferrari到达,Michael得到了通知。因为之后Sebastian也注册了该订阅,所以Michael和Sebastian都获得了新Mercedes的通知。接着Michael取消了订阅,所以只有Sebastian获得了Red Bull 的通知。
CarDealer, new car Ferrari.
Michael : Car Ferrari is new.
CarDealer, new car Mercedes.
Michael : Car Mercedes is new.
Sebastian : Car Mercedes is new.
CarDealer, new car Red Bull Racing.
Sebastian : Car Red Bull Racing is new.
4.3 弱事件
    通过事件,直接连接到发布程序和侦听器。但垃圾回收有一个问题。例如,如果侦听器不再直接饮用,发布程序就仍有一个饮用。垃圾回收器不能清空侦听器所占用的内存,因为发布程序仍保有一个饮用,会针对侦听器触发事件。
    这种强连接可以通过弱事件模式解决,即使用WeakEventManager作为发布程序和侦听器之间的中介。
一点。
    动态创建订阅事件时,为了避免出现资源泄漏,必须特别留意事件。也就是说,需要在订阅器离开作用域(不再需要它)之前,确保取消对事件的订阅。另一种方法就是使用弱事件。
1. 弱事件管理器
    要使用弱事件,需要创建一个派生自WeakEventManager类的类。WeakEventManager类在程序集WindowsBase的命名空间System.Windows中定义。
    WeakCarinfoEventManager类是弱事件管理器,它管理NewCarInfo事件的发布程序和侦听器之间的连接。因为这个类实现了单态模式,所以只创建一个实例。静态属性CurrentManager创建了一个WeakCarInfoEventManager类型的对象(如果它不存在),并返回对该对象的引用。WeakCarInfoEventManager.CurrentManager用于访问WeakCarInfoEventManager类中的单态对象。
    对于弱事件模式,弱事件管理器类需要静态方法AddListener和RemoveListener。侦听器使用这些方法连接发布程序,断开与发布程序的连接,而不是直接使用发布程序中的事件。侦听器还需要实现稍后介绍的接口IWeakEventListener。通过AddListener和RemoveListener方法,调用WeakEventManager基类中的方法,来添加和删除侦听器。
    对于WeakCarInfoEventManager类,还需要重写基类的StartListening和StopListening方法。添加第一个侦听器时调用StartListening方法,删除最后一个侦听器时调用StopListening方法。StartListening和StopListening方法从弱事件管理器中订阅和取消订阅一个方法,以侦听发布程序中的事件。如果弱事件管理器类需要连接到不同的发布程序类型上,就可以在源对象中检查类型信息,之后进行类型强制转换。接着使用基类的DeliverEvent方法,把事件传递给侦听器。DeliverEvent方法在侦听器中调用IWeakEventListener接口中的ReceiveWeakEvent方法:
 1 public class WeakCarInfoEventManager : WeakEventManager
 2 {
 3     public static void AddListener(object source, IWeakEventListener listener)
 4     {
 5         CurrentManager.ProtectedAddListener(source, listener);
 6     }
 7     public static void RemoveListener(object source, IWeakEventListener listener)
 8     {
 9         CurrentManager.ProtectedRemoveListener(source, listener);
10     }
11     public static WeakCarInfoEventManager CurrentManager
12     {
13         get
14         {
15             var manager = GetCurrentManager(typeof(WeakCarInfoEventManager)) as WeakCarInfoEventManager;
16             if (manager == null)
17             {
18                 manager = new WeakCarInfoEventManager();
19                 SetCurrentManager(typeof(WeakCarInfoEventManager), manager);
20             }
21             return manager;
22         }
23     }
24     protected override void StartListening(object source)
25     {
26         (source as CarDealer).NewCarInfo += CarDealer_NewCarInfo;
27     }
28     void CarDealer_NewCarInfo(object sender, CarInfoEventArgs e)
29     {
30         DeliverEvent(sender, e);
31     }
32     protected override void StopListening(object source)
33     {
34         (source as CarDealer).NewCarInfo -= CarDealer_NewCarInfo;
35     }
36 }
    对于发布程序类CarDealer,不需要做任何修改,其实现代码与前面相同。
2. 事件侦听器
    侦听器需要改为实现IWeakEventListener接口。这个接口定义了ReceiveWeakEvent方法,触发事件时,从弱事件管理器中调用这个方法。在该方法的实现代码中,应从触发事件中调用NewCarIsHere方法:
 1 public class Consumer : IWeakEventListener
 2 {
 3     private string name;
 4 
 5     public Consumer(string name) { this.name = name; }
 6 
 7     public void NewCarIsHere(object sender, CarInfoEventArgs e)
 8     {
 9         Console.WriteLine("{0} : Car {1} is new.", name, e.Car);
10     }
11 
12     bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
13     {
14         NewCarIsHere(sender, e as CarInfoEventArgs);
15         return true;
16     }
17 }
    在Main方法中,连接发布程序和侦听器,该连接现在使用WeakCarInfoEventManager类的AddListener和RemoveListener静态方法。
 1 static void Main(string[] args)
 2 {
 3     var dealer = new CarDealer();
 4     var michael = new Consumer("Michael");
 5     WeakCarInfoEventManager.AddListener(dealer, michael);
 6 
 7     dealer.NewCar("Ferrari");
 8 
 9     var sebastian = new Consumer("Sebastian");
10     WeakCarInfoEventManager.AddListener(dealer, sebastian);
11 
12     dealer.NewCar("Mercedes");
13 
14     WeakCarInfoEventManager.RemoveListener(dealer, michael);
15 
16     dealer.NewCar("Red Bull Racing");
17 }
    实现了弱事件模式后,发布程序和侦听器就不再强连接了。当不再引用侦听器时,它就会被垃圾回收机制回收。
4.3 泛型弱事件管理器
    .NET4.5为弱事件管理器提供了新的实现。泛型类WeakEventManager<TEventSource,TEventArgs>派生自基类WeakEventManager,它显著简化了弱事件的处理。使用这个类时,不再需要为每个事件实现一个自定义的弱事件管理器,也不需要让事件的消费者实现接口IWeakEventListener。所要做的就是使用泛型弱事件管理器订阅事件。
    订阅事件的主程序现在改为使用泛型WeakEventManager,其事件源为CarDealer类型,随事件一起传递的事件参数为CarInfoEventArgs类型。WeakEventManager类定义了AddHandler方法来订阅事件,使用RemoveHandler方法来取消订阅事件。然后,程序的工作方式与以前一样,但是代码少了许多:
 1 static void Main(string[] args)
 2 {
 3     var dealer = new CarDealer();
 4     var michael = new Consumer("Michael");
 5     WeakCarInfoEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere);
 6 
 7     dealer.NewCar("Ferrari");
 8 
 9     var sebastian = new Consumer("Sebastian");
10     WeakCarInfoEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", sebastian.NewCarIsHere);
11 
12     dealer.NewCar("Mercedes");
13 
14     WeakCarInfoEventManager<CarDealer, CarInfoEventArgs>.RemoveHandler(dealer, "NewCarInfo", michael.NewCarIsHere);
15 
16     dealer.NewCar("Red Bull Racing");
17 }
5. 小结
    本篇介绍了委托、Lambda表达式和事件的基本知识,解释了如何声明委托,如何给委托列表添加方法,如何实现通过委托和Lambda表达式调用的方法,并讨论了声明事件处理程序来相应事件的过程,以及如何创建自定义事件,使用引发事件的模式。
    .NET开发人员将大量使用委托和事件,特别是在开发Windows应用程序时。事件是.NET开发人员监控应用程序执行时出现的各种Windows消息的方式,否则就必须监控WndProc,捕获WM_MOUSEDOWN消息,而不是捕获按钮的鼠标Click事件。
    在设计大型应用程序时,使用委托和事件可以减少依赖性和层的耦合,并能开发出具有更高重用性的组件。
    Lambda表达式时委托的C#语言特性。通过它们可以减少需要编写的代码量。Lambda表达式不仅仅用于委托。详见LINQ篇

posted on 2014-12-28 11:06  熊小熊-chris  阅读(4116)  评论(0编辑  收藏  举报

导航