日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

委托、Lambda表达式和事件

發(fā)布時間:2024/4/14 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 委托、Lambda表达式和事件 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
1. 引用方法 委托是尋址方法的.NET版本。在C++中,函數(shù)指針只不過是一個指向內(nèi)存位置的指針,它不是類型安全的。我們無法判斷這個指針實(shí)際指向什么,像參數(shù)和返回類型等項(xiàng)就更無從知曉了。而.NET委托完全不同,委托是類型安全的類,它定義了返回類型和參數(shù)的類型。委托類不僅包含對方法的引用,也可以包含對多個方法的引用。 Lambda表達(dá)式與委托類型直接相關(guān)。當(dāng)參數(shù)時委托時,就可以使用Lambda表達(dá)式實(shí)現(xiàn)委托引用的方法。 2. 委托 當(dāng)要把方法傳遞給其他方法時,需要使用委托。我們習(xí)慣于把數(shù)據(jù)作為參數(shù)傳遞給方法,而有時某個方法執(zhí)行的操作并不是針對數(shù)據(jù)進(jìn)行的,而是要對另一個方法進(jìn)行操作。更麻煩的是,在編譯時我們不知道第二個方法是什么,這個信息只能在運(yùn)行時得到。所以需要把第二個方法作為參數(shù)傳遞給第一個方法。這聽起來很令人疑惑,下面用幾個例子來說明:
  • 啟動線程和任務(wù)——在C#線程的一個基類System.Threading.Thread的一個實(shí)例上使用方法Start(),就可以啟動一個線程。如果要告訴計算機(jī)啟動一個新的執(zhí)行序列,就必須說明要在哪里啟動該序列。必須為計算機(jī)提供開始啟動的方法的細(xì)節(jié),即Thread類的構(gòu)造函數(shù)必須帶有一個參數(shù),該參數(shù)定義了線程調(diào)用的方法。
  • 通用庫類——比如Sort(List<T> list,Func<T, T, bool> comparison)函數(shù)實(shí)現(xiàn)快速排序,則需要指定一個方法參數(shù)comparison,告訴排序函數(shù)如何實(shí)現(xiàn)對兩個參數(shù)的比較。
  • 事件——一般是通知代碼發(fā)生了什么事件。GUI編程主要處理事件。在引發(fā)事件時,運(yùn)行庫需要知道應(yīng)執(zhí)行哪個方法。這就需要把處理事件的方法作為一個參數(shù)傳遞給委托。
在C和C++中,只能提取函數(shù)的地址,并作為一個參數(shù)傳遞它。C沒有類型安全性。可以把任何函數(shù)傳遞給需要函數(shù)指針的方法。但是,這種直接方法不僅會導(dǎo)致一些關(guān)于類型安全性的問題,而且沒有意識到:在進(jìn)行面向?qū)ο缶幊虝r,幾乎沒有方法是孤立存在的,而是在調(diào)用方法前通常需要與類實(shí)例相關(guān)聯(lián)。所以.NET Framework在語法上不允許使用這種直接方法。如果要傳遞方法,就必須把方法的細(xì)節(jié)封裝在一種新類型的對象中,即委托。委托只是一種特殊類型的對象,其特殊之處在于,我們以前定義的對象都包含數(shù)據(jù),而委托包含的只是一個或多個方法的地址。 2.1聲明委托 使用委托時,首先需要定義要使用的委托,對于委托,定義它就是告訴編譯器這種類型的委托表示哪種類型的方法。然后,必須創(chuàng)建該委托的一個或多個實(shí)例。編譯器在后臺將創(chuàng)建表示該委托的一個類。 定義為托的語法如下: delegate?void IntMethodInvoker(int x); 在這個示例中,定義了一個委托IntMethodInvoker,并指定該委托的每個實(shí)例都可以包含一個方法的引用,該方法帶有一個int參數(shù),并返回void。理解委托的一個要點(diǎn)是它們的類型安全性非常高。在定義委托時,必須給出它所表示的方法的簽名和返回類型等全部細(xì)節(jié)(理解委托的一種好方式是把委托當(dāng)做這樣一件事情:它給方法的簽名和返回類型指定名稱) ? ? 假定要定義一個委托TwoLongsOp,該委托表示的方法有兩個long型參數(shù),返回類型為double,可以編寫如下代碼: ? ??delegate?double TwoLongsOp(long first, long second); ? ? 或者要定義一個委托,它表示的方法不帶參數(shù),返回一個string型的值,可以編寫如下代碼: ? ??delegate?string GetAString(); ? ? 其語法類似于方法的定義,但沒有方法體,定義的前面要加上關(guān)鍵字delegate。因?yàn)槎x委托基本上是定義一個新類,所以可以在定義類的任何相同地方定義委托,也就是說,可以在另一個類的內(nèi)部定義,也可以在任何類的外部定義,還可以在命名空間中把委托定義為頂層對象。根據(jù)定義的可見性。和委托的作用域,可以在委托的定義上應(yīng)用任意常見的訪問修飾符:public、private、protected等。 ? ? 實(shí)際上,“定義一個委托”是指“定義一個新類”。委托實(shí)現(xiàn)為派生自基類System.MulticastDelegate的類,System.MulticastDelegate又派生自其基類System.Delegate。C#編譯器能識別這個類,會使用其委托語法,因此我們不需要了解這個類的具體執(zhí)行情況。這是C#與基類共同合作,是編程更易完成的另一個范例。 ? ? 定義好委托后,就可以創(chuàng)建它的一個實(shí)例,從而用它存儲特定方法的細(xì)節(jié)。 ? ? 但是,在術(shù)語方面有一個問題。類有兩個不同的術(shù)語:“類”表示比較廣義的定義,“對象”表示類的實(shí)例。但委托只有一個術(shù)語。在創(chuàng)建委托的實(shí)例時,所創(chuàng)建的委托的實(shí)例仍成為委托。必須從上下文中確定委托的確切含義。 2.2 ?使用委托 下面的代碼說明了如何使用委托。這是在int上調(diào)用ToString()方法的一種相當(dāng)冗長的方式: 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()); } 在這段代碼中,實(shí)例化了類型為GetAString的一個委托,并對它進(jìn)行初始化,使用它引用整型變量x的ToString()方法。在C#中,委托在語法上總是接受一個參數(shù)的構(gòu)造函數(shù),這個參數(shù)就是委托引用的方法。這個方法必須匹配最初定義委托時的簽名。 為了減少輸入量,只要需要委托實(shí)例,就可以只傳送地址的名稱。這稱為委托推斷。只要編譯器可以把委托實(shí)例解析為特定的類型,這個C#特性就是有效的。下面兩個語句是等效的。 GetAString firstStringMethod = new GetAString(x.ToString); GetAString firstStringMethod = x.ToString; C#編譯器創(chuàng)建的代碼是一樣的。 委托推斷可以在需要委托實(shí)例的任何地方使用。委托推斷也可以用于事件,因?yàn)槭录腔谖械摹?委托的一個特性是它們的類型是安全的,可以確保被調(diào)用的方法的簽名是正確的。但是有趣的是。它們不關(guān)心在什么類型的對象上調(diào)用該方法,甚至不考慮該方法是靜態(tài)方法,還是實(shí)例方法。 ? ? 給定委托的實(shí)例可以引用任何類型的任何對象上的實(shí)例方法或靜態(tài)方法——只要方法的簽名匹配于委托的簽名即可。 2.3 Action<T>和Func<T>委托 除了為每個參數(shù)和返回類型定義一個新類型委托類型之外,還可以使用Action<T>和Func<T>委托。泛型Action<T>委托表示引用一個void返回類型的方法。這個委托類存在不同的變體,可以傳遞至多16種不同的參數(shù)類型。沒有泛型參數(shù)的Action類可調(diào)用沒有參數(shù)的方法。Action<in T>調(diào)用帶一個參數(shù)的方法,Action<in T2, in T2>調(diào)用帶兩個參數(shù)的方法,以此類推。 Func<T>委托可以以類似的方式使用。Func<T>允許調(diào)用帶返回類型的方法。與Action<T>類似,Func<T>也定義了不同的變體,至多也可以傳遞16個參數(shù)類型和一個返回值類型。Func<out TResult>委托類型可以調(diào)用帶返回類型且無參數(shù)的方法,Func<in T, out TResult>調(diào)用帶一個參數(shù)的方法,以此類推。 2.4 多播委托 前面使用的每個委托都只包含一個方法調(diào)用。調(diào)用委托的次數(shù)與調(diào)用方法的次數(shù)相同。如果要調(diào)用多個方法,就需要多次顯式調(diào)用這個委托。但是,委托也可以包含多個方法。這種委托成為多播委托。如果調(diào)用多播委托,就可以按順序連續(xù)調(diào)用多個方法。為此,委托的簽名就必須返回void;否則,就只能得到委托調(diào)用后最后一個方法的結(jié)果。 如果正在使用多播委托,就應(yīng)知道對同一個委托調(diào)用方法鏈的順序并未正式定義。因此應(yīng)避免編寫依賴于特定順序調(diào)用方法的代碼。 通過一個委托調(diào)用多個方法還可能導(dǎo)致一個大問題。多播委托包含一個逐個調(diào)用的委托集合,如果通過委托調(diào)用的其中一個方法拋出一個異常,整個迭代就會停止。在這種情況下,為了避免這個問題,應(yīng)自己迭代方法列表。Delegate類定義GetInvocationList()方法,它返回一個Delegate對象數(shù)組。現(xiàn)在可以使用這個委托調(diào)用與委托直接相關(guān)的方法,捕獲異常,并繼續(xù)下一次迭代。 Delegate[] delegates = firstStringMethod.GetInvocationList();foreach (Action d in delegates){try{d();}catch (Exception e){Console.WriteLine(e.Message);}} 2.5 匿名方法 到目前為止,要想使委托工作,方法必須已經(jīng)存在(即委托是用它將調(diào)用的方法的相同簽名定義的)。但還有另外一種使用委托的方式:即通過匿名方法。匿名方法是用作委托的參數(shù)的一段代碼。用匿名方法定義委托的語法與前面的定義并沒有區(qū)別。但在實(shí)例化委托時,就有區(qū)別了。 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")); 匿名方法的優(yōu)點(diǎn)是減少了要編寫的代碼。不必定義僅由委托使用的方法。在為事件定義委托時,這是非常顯然的。這有助于降低代碼的復(fù)雜性,尤其是定義了好幾個事件時,代碼會顯得比較簡單。使用匿名方法時,代碼執(zhí)行速度并沒有加快。編譯器仍定義了一個方法,該方法只有一個自動指定的名稱,我們不需要知道這個名稱。 使用匿名方法時必須遵循兩個規(guī)則。在匿名方法中不能使用跳轉(zhuǎn)語句(break、goto或continue)跳到該匿名方法的外部,反之亦然:匿名方法外部的跳轉(zhuǎn)語句也不能跳到該匿名方法的內(nèi)部。 在匿名方法內(nèi)部不能訪問不安全的代碼。另外,也不能訪問在匿名方法外部使用的ref和out參數(shù)。但可以使用在匿名方法外部定義的其他變量。 如果需要用匿名方法多次編寫同一個功能,就不要使用匿名方法。此時與復(fù)制代碼相比,編寫一個命名方法比較好。從C#3.0開始,可以使用Lambda表達(dá)式替代匿名方法。有關(guān)匿名方法和Lambda表達(dá)式的區(qū)別,參考本分類下的《匿名方法和Lambda表達(dá)式3. Lambda表達(dá)式 自從C#3.0開始,就可以使用一種新語法把實(shí)現(xiàn)代碼賦予委托:Lambda表達(dá)式。只要有委托參數(shù)類型的地方,就可以使用Lambda表達(dá)式。前面使用匿名方法的例子可以改為使用Lambda表達(dá)式: 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表達(dá)式運(yùn)算符“=>”的左邊列出了需要的參數(shù)。Lambda運(yùn)算符的右邊定義了賦予lambda變量的方法的實(shí)現(xiàn)代碼。 3.1 參數(shù) Lambda表達(dá)式有幾種定義參數(shù)的方式。如果只有一個參數(shù),只寫出參數(shù)名就可以了,例如上面所示的代碼。如果委托使用多個參數(shù),就把參數(shù)名放在花括號中。例如: Func<double, double, double> twoParams = (x, y) => x * y;Console.WriteLine(twoParams(3, 2)); 為了方便,可以在花括號中給變量名添加參數(shù)類型。如果編譯器不能匹配重載后的版本,那么使用參數(shù)類型可以幫助找到匹配的委托: Func<double, double, double> twoParams = (double x, double y)=> x * y; Console.WriteLine(twoParams(3, 2)); 3.2 多行代碼 如果Lambda表達(dá)式只有一條語句,在方法塊內(nèi)就不需要花括號和return語句,因?yàn)榫幾g器會添加一條隱式的return語句。但是如果在Lambda表達(dá)式的實(shí)現(xiàn)代碼中需要多條語句,就必須添加花括號和return語句。例如本節(jié)開始的代碼。 3.3 閉包 通過Lambda表達(dá)式可以訪問表達(dá)式外部的變量,這成為閉包。閉包是一個非常好的功能,但如果未正確使用,也會非常危險。特別是,通過另一個線程調(diào)用拉姆達(dá)表達(dá)式時,當(dāng)前局部變量的值是不確定的。 int someValue=5; Func<int, int> f = x=> x + someValue; 對于Lambda表達(dá)式x=>x+someValue,編譯器會創(chuàng)建一個匿名類,它有一個構(gòu)造函數(shù)來傳遞外部變量。該構(gòu)造函數(shù)取決于從外部傳遞進(jìn)來的變量個數(shù)。對于這個簡單的例子,構(gòu)造函數(shù)接受一個int。匿名類包含一個匿名方法,其實(shí)現(xiàn)代碼、參數(shù)和返回類型由lambda表達(dá)式定義: public class AnonymousClass{private int someValue;public AnonymousClass(int someValue){this.someValue = someValue;}public int AnonymousMethod(int x){return x + someValue;}} 使用Lambda表達(dá)式并調(diào)用該方法,會創(chuàng)建匿名類的一個實(shí)例,并傳遞調(diào)用該方法時變量的值。 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中,這段代碼的執(zhí)行結(jié)果發(fā)生了變化。使用C#4或更早版本的編譯器時,會在控制臺中輸出3次30.在第一個foreach循環(huán)中使用閉包時,所創(chuàng)建的函數(shù)是在調(diào)用時,而不是迭代時獲得val變量的值。編譯器會從foreach語句創(chuàng)建一個while循環(huán)。在C#4中,編譯器在while循環(huán)外部定義循環(huán)變量,在每次迭代中重用這個變量。因此,在循環(huán)結(jié)束時,該變量的值就是最后一次迭代的值。要想在使用C#4時讓代碼的結(jié)果為10、20、30,必須將代碼改為使用一個局部變量,并將這個局部變量傳入Lambda表達(dá)式。這樣,每次迭代時就將保留一個不同的值。 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循環(huán)的代碼塊中創(chuàng)建一個不同的局部循環(huán)變量,所以值會自動得到保留。這是C#4與C#5.0的區(qū)別,必須知道這一點(diǎn)。 ? ? Lambda表達(dá)式可以用于類型為委托的任意地方。類型是Expression或Expression<T>時,也可以使用Lambda表達(dá)式,此時編譯器會創(chuàng)建一個表達(dá)式樹。 4. 事件 事件基于委托,為委托提供了一種發(fā)布/訂閱機(jī)制。在架構(gòu)內(nèi)到處都能看到事件。在Windows應(yīng)用程序中,Button類提供了Click事件。這類事件就是委托。觸發(fā)Click事件時調(diào)用的處理程序方法需要定義,其參數(shù)由委托類型定義。 在本節(jié)的示例代碼中,事件用于連接CarDealer類和Consumer類。CarDealer類提供了一個新車到達(dá)時觸發(fā)的事件。Consumer類訂閱該事件,以獲得新車到達(dá)的通知。 4.1 事件發(fā)布程序 從CarDealer類開始,它基于事件提供一個訂閱。CarDealer類用event關(guān)鍵字定義了類型為EventHandler<CarInfoEventArgs>的NewCarInfo事件。在NewCar()方法中,通過調(diào)用RaiseNewCarInfo方法觸發(fā)NewCarInfo事件。這個方法的實(shí)現(xiàn)檢查委托是否為空,如果不為空,就引發(fā)事件。 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事件。作為一個約定,事件一般使用帶兩個參數(shù)的方法,其中第一個參數(shù)是一個對象,包含事件的發(fā)送者。第二個參數(shù)提供了事件的相關(guān)信息。第二個參數(shù)隨不同的事件類型而不同。.NET1.0為所有不同數(shù)據(jù)類型的事件定義了幾百個委托,有了泛型委托EventHandler<T>之后,就不再需要委托了。EventHandler<TEventArgs>定義了一個處理程序,它返回void,接受兩個參數(shù)。對于EventHandler<TEventArgs>,第一個參數(shù)必須是object類型,第二個參數(shù)是T類型。EventHandler<TEventArgs>還定義了一個關(guān)于T的約束:它必須派生自基類EventArgs。CarInfoEventArgs就派生自基類EventArgs。 委托EventHandler<TEventArgs>的定義如下: public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)where TEventArgs:EventArgs; 在一行上定義事件是C#的簡化記法。編譯器會創(chuàng)建一個EventHandler<CarInfoEventArgs>委托類型的變量,以便從委托中訂閱和取消訂閱。該簡化記法的較長形式如下所示。這非常類似于自動屬性和完整屬性之間的關(guān)系。對于事件,使用add和remove關(guān)鍵字添加和刪除委托的處理程序: private EventHandler<CarInfoEventArgs> newCarInfo;//原文此處有誤:private delegate EventHandler<CarInfoEventArgs> newCarInfo;不能通過編譯(C#高級編程第八版) public event EventHandler<CarInfoEventArgs> NewCarInfo {add { newCarInfo += value; }remove { newCarInfo -= value; } } ? ? 如果不僅僅需要添加和刪除事件處理程序,定義事件的長記法就很有用。例如,需要為多個線程訪問添加同步操作。WPF控件使用長記法給事件添加冒泡和隧道功能。 CarDealer類在RaiseNewCarInfo方法中觸發(fā)事件。使用NewCarInfo和花括號可以調(diào)用給定事件訂閱的所有處理程序。注意與多播委托一樣,方法的調(diào)用順序無法保證。為了更多地控制處理程序的調(diào)用,可以使用Delegate類的GetInvocationList方法訪問委托列表中的每一項(xiàng),并獨(dú)立地調(diào)用每個方法。在觸發(fā)事件之前,需要檢查委托NewCarInfo是否不為空。如果沒有訂閱處理程序,委托就是空。 4.2 事件偵聽器 Consumer類用作事件偵聽器。這個類訂閱了CarDealer類的事件,并定義了NewCarIsHere方法,該方法滿足EventHandler<CarInfoEventArgs>委托的要求,其參數(shù)類型是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);} } 現(xiàn)在需要連接事件發(fā)布程序和訂閱器。為此使用CarDealer類的NewCarInfo事件,通過“+=”創(chuàng)建一個訂閱。消費(fèi)者micheal(變量)訂閱了事件,接著消費(fèi)者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 } 運(yùn)行應(yīng)用程序,一輛Ferrari到達(dá),Michael得到了通知。因?yàn)橹骃ebastian也注冊了該訂閱,所以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 弱事件 通過事件,直接連接到發(fā)布程序和偵聽器。但垃圾回收有一個問題。例如,如果偵聽器不再直接飲用,發(fā)布程序就仍有一個飲用。垃圾回收器不能清空偵聽器所占用的內(nèi)存,因?yàn)榘l(fā)布程序仍保有一個飲用,會針對偵聽器觸發(fā)事件。 這種強(qiáng)連接可以通過弱事件模式解決,即使用WeakEventManager作為發(fā)布程序和偵聽器之間的中介。 一點(diǎn)。 ? ? 動態(tài)創(chuàng)建訂閱事件時,為了避免出現(xiàn)資源泄漏,必須特別留意事件。也就是說,需要在訂閱器離開作用域(不再需要它)之前,確保取消對事件的訂閱。另一種方法就是使用弱事件。 1. 弱事件管理器 要使用弱事件,需要創(chuàng)建一個派生自WeakEventManager類的類。WeakEventManager類在程序集WindowsBase的命名空間System.Windows中定義。 WeakCarinfoEventManager類是弱事件管理器,它管理NewCarInfo事件的發(fā)布程序和偵聽器之間的連接。因?yàn)檫@個類實(shí)現(xiàn)了單態(tài)模式,所以只創(chuàng)建一個實(shí)例。靜態(tài)屬性CurrentManager創(chuàng)建了一個WeakCarInfoEventManager類型的對象(如果它不存在),并返回對該對象的引用。WeakCarInfoEventManager.CurrentManager用于訪問WeakCarInfoEventManager類中的單態(tài)對象。 對于弱事件模式,弱事件管理器類需要靜態(tài)方法AddListener和RemoveListener。偵聽器使用這些方法連接發(fā)布程序,斷開與發(fā)布程序的連接,而不是直接使用發(fā)布程序中的事件。偵聽器還需要實(shí)現(xiàn)稍后介紹的接口IWeakEventListener。通過AddListener和RemoveListener方法,調(diào)用WeakEventManager基類中的方法,來添加和刪除偵聽器。 對于WeakCarInfoEventManager類,還需要重寫基類的StartListening和StopListening方法。添加第一個偵聽器時調(diào)用StartListening方法,刪除最后一個偵聽器時調(diào)用StopListening方法。StartListening和StopListening方法從弱事件管理器中訂閱和取消訂閱一個方法,以偵聽發(fā)布程序中的事件。如果弱事件管理器類需要連接到不同的發(fā)布程序類型上,就可以在源對象中檢查類型信息,之后進(jìn)行類型強(qiáng)制轉(zhuǎn)換。接著使用基類的DeliverEvent方法,把事件傳遞給偵聽器。DeliverEvent方法在偵聽器中調(diào)用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 } ? ? 對于發(fā)布程序類CarDealer,不需要做任何修改,其實(shí)現(xiàn)代碼與前面相同。 2. 事件偵聽器 偵聽器需要改為實(shí)現(xiàn)IWeakEventListener接口。這個接口定義了ReceiveWeakEvent方法,觸發(fā)事件時,從弱事件管理器中調(diào)用這個方法。在該方法的實(shí)現(xiàn)代碼中,應(yīng)從觸發(fā)事件中調(diào)用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方法中,連接發(fā)布程序和偵聽器,該連接現(xiàn)在使用WeakCarInfoEventManager類的AddListener和RemoveListener靜態(tài)方法。 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 } 實(shí)現(xiàn)了弱事件模式后,發(fā)布程序和偵聽器就不再強(qiáng)連接了。當(dāng)不再引用偵聽器時,它就會被垃圾回收機(jī)制回收。 4.3 泛型弱事件管理器 .NET4.5為弱事件管理器提供了新的實(shí)現(xiàn)。泛型類WeakEventManager<TEventSource,TEventArgs>派生自基類WeakEventManager,它顯著簡化了弱事件的處理。使用這個類時,不再需要為每個事件實(shí)現(xiàn)一個自定義的弱事件管理器,也不需要讓事件的消費(fèi)者實(shí)現(xiàn)接口IWeakEventListener。所要做的就是使用泛型弱事件管理器訂閱事件。 訂閱事件的主程序現(xiàn)在改為使用泛型WeakEventManager,其事件源為CarDealer類型,隨事件一起傳遞的事件參數(shù)為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. 小結(jié) 本篇介紹了委托、Lambda表達(dá)式和事件的基本知識,解釋了如何聲明委托,如何給委托列表添加方法,如何實(shí)現(xiàn)通過委托和Lambda表達(dá)式調(diào)用的方法,并討論了聲明事件處理程序來相應(yīng)事件的過程,以及如何創(chuàng)建自定義事件,使用引發(fā)事件的模式。 .NET開發(fā)人員將大量使用委托和事件,特別是在開發(fā)Windows應(yīng)用程序時。事件是.NET開發(fā)人員監(jiān)控應(yīng)用程序執(zhí)行時出現(xiàn)的各種Windows消息的方式,否則就必須監(jiān)控WndProc,捕獲WM_MOUSEDOWN消息,而不是捕獲按鈕的鼠標(biāo)Click事件。 在設(shè)計大型應(yīng)用程序時,使用委托和事件可以減少依賴性和層的耦合,并能開發(fā)出具有更高重用性的組件。 Lambda表達(dá)式時委托的C#語言特性。通過它們可以減少需要編寫的代碼量。Lambda表達(dá)式不僅僅用于委托。詳見LINQ篇。

轉(zhuǎn)載于:https://www.cnblogs.com/ChrisLi/p/4189724.html

總結(jié)

以上是生活随笔為你收集整理的委托、Lambda表达式和事件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。