委託、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篇
相關文章
相關標籤/搜索