委托、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时让代码的结果为十、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篇
相关文章
相关标签/搜索