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篇。