委托是一種引用類型.是能夠持有方法的類類型,他是尋址方式的C#實(shí)現(xiàn).和其他的普通類不一樣的是,委托具有簽名,同時只能使用相同簽名的函數(shù).委托與C++中的函數(shù)指針比較類似.允許你傳遞一個放到到另一個方法中,使得這個方法中可以直接調(diào)用傳入的函數(shù)或者方法. 在引用非靜態(tài)成員函數(shù)時,delegate不但保存了對此函數(shù)入口地址的引用,而且還保存了調(diào)用此函數(shù)的類實(shí)例的引用.與函數(shù)指針相比,delegate是面向?qū)ο蟆㈩愋桶踩⒖煽康氖芸貙ο?也就是說,runtime能夠保證delegate指向一個有效的方法,你無須擔(dān)心delegate會指向無效地址或者越界地址.委托還能夠包括對一組方法的引用.
委托的屬性(MSDN的定義):
l 委托類似于 C++ 函數(shù)指針,但它們是類型安全的.
l 委托允許將方法作為參數(shù)進(jìn)行傳遞.
l 委托可用于定義回調(diào)方法.
l 委托可以鏈接在一起;例如,可以對一個事件調(diào)用多個方法.
l 方法不必與委托類型完全匹配.
如果足夠細(xì)心那么就可以看出其實(shí)委托和接口有一定的相似. 委托和接口都允許類設(shè)計器分離類型聲明和實(shí)現(xiàn).任何類或結(jié)構(gòu)都能繼承和實(shí)現(xiàn)給定的接口.可以為任何類上的方法創(chuàng)建委托,前提是該方法符合委托的方法簽名.接口引用或委托可由不了解實(shí)現(xiàn)該接口或委托方法的類的對象使用.其實(shí)委托和接口都是定一個規(guī)范.只要按照此規(guī)范來實(shí)現(xiàn)的東西都可以被接受.那么接口和委托在使用應(yīng)該怎么去考慮呢:
當(dāng)出現(xiàn)以下情況出現(xiàn)的時候可以考慮使用委托:
l 當(dāng)定義事件的時候.
l 當(dāng)需要構(gòu)成方法鏈的時候.
l 當(dāng)類可能需要該方法的多個實(shí)現(xiàn)的時候,委托鏈就是個例子
l 當(dāng)在多線程或者異步環(huán)境中需要通知其他程序的時候.
l 當(dāng)封裝靜態(tài)方法的時候
l 當(dāng)調(diào)用方不需要訪問實(shí)現(xiàn)該方法的對象中的其他屬性、方法或接口時.
當(dāng)出現(xiàn)以下情況的時候可以考慮使用接口:
l 當(dāng)存在一組可能被調(diào)用的相關(guān)方法時.
l 當(dāng)類只需要方法的單個實(shí)現(xiàn)時.
l 當(dāng)使用接口的類想要將該接口強(qiáng)制轉(zhuǎn)換為其他接口或類類型時.
l 當(dāng)正在實(shí)現(xiàn)的方法鏈接到類的類型或標(biāo)識時:例如比較方法.IComparable 或泛型版本 IComparable<T> 就是一個使用單一方法接口而不使用委托的很好的示例. IComparable 聲明 CompareTo 方法,該方法返回一個整數(shù),指定相同類型的兩個對象之間的小于、等于或大于關(guān)系. IComparable 可用作排序算法的基礎(chǔ). 雖然將委托比較方法用作排序算法的基礎(chǔ)是有效的,但是并不理想. 因?yàn)檫M(jìn)行比較的能力屬于類,而比較算法不會在運(yùn)行時改變,所以單一方法接口是理想的
委托的使用在C#中相當(dāng)?shù)膹V泛.例如初始化一個線程的時候,傳入的參數(shù)ThreadStart類型就是就一個委托.
委托的原型如下:public abstract class Delegate : ICloneable, ISerializable從這個原型可以看出delegate類其實(shí)就是一個抽象類.在這里不要看到這個Delegate是一個類就可以去繼承,微軟是不允許繼承的.不過透過源碼還是可以看到這個類的內(nèi)部其實(shí)還是保存了一個方法的地址句柄.我們?nèi)粘6x委托的時候其實(shí)不是直接繼承的這個類而是繼承的MulticastDelegate這個類.其定義的原型如下:public abstract class MulticastDelegate : Delegate從這個定義中可以看出這個也是一個抽象類,但是這也是一個特殊類,一樣不允許繼承.在我們定義委托后編譯器會自動識別這兩個類然后編譯成委托類.委托還有一種用法,就是異步委托.這個在線程中講過.這里不再更多的詳細(xì)的講了.關(guān)于委托的內(nèi)部實(shí)現(xiàn),有興趣的朋友可以去仔細(xì)看看這個類的內(nèi)部實(shí)現(xiàn)代碼,其實(shí)這個類的內(nèi)部實(shí)現(xiàn)并沒有想象的復(fù)雜,只是他調(diào)用了一堆外部方法沒有公開源碼(不知道這部分的內(nèi)部實(shí)現(xiàn)如何).另外如果想了解他對方法的調(diào)用可以去看看MothedInfo的內(nèi)部代碼. https://referencesource.microsoft.com/#mscorlib/system/delegate.cs定義一個委托其實(shí)嚴(yán)格來說是定義了一個類.然后在聲明一個對應(yīng)的對象并實(shí)例化.
例子如下:
classtestDelegate
{
delegatevoidtestHandler(int i);
publicvoid test()
{
testHandler ttt =newtestHandler(t2);
ttt.Invoke(1000);
}
void t2(int i)
{
Console.WriteLine("delegate call para=" + i);
}
}
輸出結(jié)果:
3.3Action<T>和Func<T>
Action或者Action<T>這類型的委托是不具有返回值的系統(tǒng)定義的委托.Func<T>是系統(tǒng)定義的具有返回值的委托. 這兩個類都有很多的重載版本.Action和Func的出現(xiàn)其實(shí)是為了減少開發(fā)人員對委托的定義,可以直接使用.定義一個委托其實(shí)就是定義了一個新類,雖然代碼不多,但還是要寫一點(diǎn)點(diǎn)的.
例子如下:
classtestActionAndFunc
{
publicvoid test()
{
Action<string> a = x =>
{
Console.WriteLine("這里輸入的是:" + x);
};
Func<string,string> f = x =>
{
Console.WriteLine("func的輸入是:"+ x);
return"this is func";
};
a("調(diào)用Aciton");
var r= f("call func");
Console.WriteLine("f Return result:" + r);
}
}
結(jié)果如下:
上面的代碼是直接定義的委托類型,在委托的實(shí)現(xiàn)中使用的是lambda表達(dá)式的方式來完成的.具體lambda的內(nèi)容后面將會講到.這樣寫其實(shí)就是讓代碼變得更加簡介.不過從另一個方面來說如果不熟悉這種寫發(fā)的人看起來會比較吃力.相當(dāng)于提高了讀代碼的門檻.
3.4多路廣播委托
多路委托其實(shí)就是將多個委托合并一起調(diào)用.我們上面的例子中調(diào)用委托的次數(shù)和調(diào)用方法的次數(shù)是一樣的.這樣的就是單路委托.委托中還有一個很有用的屬性就是可以使用”+”號將多個委托對象鏈接起來,串成一串,這樣在調(diào)用的時候也是調(diào)用一串.如果你看其中某個委托實(shí)現(xiàn)不順眼可以使用”-”將其去掉. 多路委托包含已分配委托的列表. 在調(diào)用多播委托時,它會按順序調(diào)用列表中的委托. 只能合并相同類型的委托.但是列表中的順序是未知的,因此在使用多路委托的時候需要注意不要讓其他的委托依賴于某個委托執(zhí)行的結(jié)果.注意多路委托也是繼承自MulticastDelegate類的.
例子如下:
classtestMulticastDelegate
{
publicvoid test()
{
Action aciton = a;
aciton += b;
aciton += c;
aciton();
Console.WriteLine("delete a");
aciton -= a;
aciton();
Console.WriteLine("delete b add a");
aciton -= b;
aciton += a;
aciton();
}
void a()
{
Console.WriteLine("call a");
}
void b()
{
Console.WriteLine("call b");
}
void c()
{
Console.WriteLine("call c");
}
}
輸出結(jié)果如下:
3.5委托的協(xié)變與逆變
協(xié)變與逆變其實(shí)說簡單點(diǎn)就是類型轉(zhuǎn)化.我們知道委托其實(shí)是一個類.類是具有繼承性的.在面向?qū)ο蟮氖澜缋镉幸粋€很著名的原則叫里氏代換,任何可以用父類的地方都可以用子類進(jìn)行代替.基于這個原則來理解協(xié)變就容易了,但是這里有點(diǎn)點(diǎn)區(qū)別.協(xié)變的定義:讓一個帶有協(xié)變參數(shù)的泛型接口(或委托)可以接收類型更加精細(xì)化,具體化的泛型接口(或委托)作為參數(shù),可以看成面向?qū)ο笾卸鄳B(tài)的一個延伸. 協(xié)變允許方法具有與接口的泛型類型參數(shù)所定義的返回類型相比派生程度更大的返回類型.
舉個例子:
classtestFT
{
publicvoid test()
{
IEnumerable<String> t1 = newList<String>(newstring[] { "123","456" });
IEnumerable<Object> t2 = t1;
foreach(var x in t2)
{
Console.WriteLine(x);
}
}
}
在這段代碼中可以看出object是string類型的基類,這種轉(zhuǎn)變方式是復(fù)合上面協(xié)變的要求和過程.但是這里有一點(diǎn)注意的就是IEnumerable<string>接口不是繼承自IEnumerable<object>接口,不過呢,string卻是繼承自object.因此是符合協(xié)變定義,所以IEnumerable<T> 接口為協(xié)變接口.
那么逆變呢?其實(shí)理解了協(xié)變在來理解逆變就相對簡單多了,反正都是針對具體參數(shù)來說明的.逆變定義: 讓一個帶有協(xié)變參數(shù)的泛型接口(或委托)可以接收粒度更粗的泛型接口或委托作為參數(shù),這個過程實(shí)際上是參數(shù)類型更加精細(xì)化的過程. 逆變允許方法具有與接口的泛型形參所指定的實(shí)參類型相比派生程度更小的實(shí)參類型.其實(shí)逆變簡單來說就是針對帶有返回類型的委托的參數(shù)來說明的.
下面來個簡單的例子:
classtestNB
{
classb1
{ }
classb1_1 :b1
{ }
classtbc :IComparable<b1>
{
publicint GetHashCode(b1 baseInstance)
{
return baseInstance.GetHashCode();
}
publicbool Equals(b1 x, b1 y)
{
return x == y;
}
publicint CompareTo(b1 other)
{
returnthis.GetHashCode() ==other.GetHashCode() ? 0 : 1;
}
}
publicvoid test()
{
IComparable<b1> bc = newtbc();
IComparable<b1_1> cc = bc;
}
}
從這個例子中可以看出第一b1_1這個類是繼承自b1的,類tbc是繼承自ICamparable<b1>的,下面test方法中泛型內(nèi)的兩個b1是基類類型,b1_1是子類類型.但這里的基類的實(shí)例bc是指向了子tbc類的一個實(shí)例.因此在后面逆變才能夠成功.
其實(shí)泛型的協(xié)變和逆變是針對泛型的參數(shù)和泛型接口的返回值來說的,不是針對泛型本身的.
下面這些接口是常用的協(xié)變與逆變接口
l IEnumerable<T>(T 為協(xié)變)
l IEnumerator<T>(T 為協(xié)變)
l IQueryable<T>(T 為協(xié)變)
l IGrouping<TKey,?TElement>(TKey 和 TElement 為協(xié)變)
l IComparer<T>(T 為逆變)
l IEqualityComparer<T>(T 為逆變)
l IComparable<T>(T 為逆變)
注意:
1 協(xié)變與逆變中的所有類型都引用類型,值類型是無法完成協(xié)變與逆變的.
2 若要啟用隱式轉(zhuǎn)換,必須使用 in 或 out 關(guān)鍵字,將委托中的泛型參數(shù)顯式聲明為協(xié)變或逆變.如果僅使用變體支持來匹配方法簽名和委托類型,且不使用 in 和 out 關(guān)鍵字,你會發(fā)現(xiàn)用相同的 lambda 表達(dá)式或方法來實(shí)例化多個委托,但無法將一個委托指派給另一個委托.
上面說了這么多其實(shí)都是說的接口和泛型的逆變與協(xié)變.下面來說說委托的協(xié)變與逆變.
用于在 C#的所有委托中匹配方法簽名和委托類型.意思就是不僅可以為委托指派具有匹配簽名的方法,而且可以指派返回與委托類型指定的派生類型相比,派生程度更大的類型(協(xié)變),或者接受相比之下,派生程度更小的類型的參數(shù)(逆變). 這包括泛型委托和非泛型委托.這個有點(diǎn)拗口,意思就是,在指定委托的時候你可以使用基類類型來代替定義中所使用的子類類型.也可以使用更小的子類類型來代替基類類型.
下面舉個例子來說明委托的協(xié)變:
classtestDelegateBB
{
classparent
{
publicvirtualvoid tt()
{
Console.WriteLine("parent tt");
}
}
classson :parent
{
publicoverridevoid tt()
{
Console.WriteLine("son tt");
}
}
delegateparentphandler();
publicvoid test()
{
phandler ph = p1;
var x = ph.Invoke();
x.tt();
ph = p2;
var xx = ph.Invoke();
xx.tt();
}
parent p1() {returnnewparent(); }
son p2() {returnnewson(); }
}
輸出結(jié)果:
這里就發(fā)生協(xié)變,委托類型聲明的時候不是子類son,但是son繼承了父類parent,因此這個操作是可以通過的.
委托的逆變:
classtestDelegateNB
{
classpara { }
classp1 :para { }
classp2 :para { }
delegatevoidph1(p1p);
delegatevoidph2(p2p);
publicvoid test()
{
ph1 px1 = tt;
ph2 px2 = tt;
p1 p_1 =newp1();
p2 p_2 =newp2();
px1.Invoke(p_1);
px2.Invoke(p_2);
}
void tt(para p)
{
Console.WriteLine("ok");
}
}
在這個例子中兩個委托的簽名是不一樣的.但是卻可以指向同一個方法.這就逆變的作用.逆變在C#自身中有很多地方都有實(shí)現(xiàn).常見的就是窗體界面的很多事件都可以指向同一個實(shí)現(xiàn)方法.
比如:
PRivatevoid MultiHandler(object sender, System.EventArgs e)
{ }
this.button1.KeyDown +=this.MultiHandler;
this.button1.MouseClick +=this.MultiHandler;
這個代碼在實(shí)現(xiàn)的編程中可以幫你減少很多代碼.很常用的方法.但實(shí)際上兩個事件的簽名不完全相同,只不過都有共同的基類.
說了這么多其實(shí)委托的協(xié)變與逆變總結(jié)下來很簡單的:
協(xié)變和逆變會提供用于使委托類型與方法簽名匹配的靈活性.協(xié)變允許方法具有的派生返回類型比委托中定義的更多. 逆變允許方法具有的派生參數(shù)類型比委托類型中的更少.說白了就是協(xié)變主要是針對參數(shù)來說的,逆變主要是針對返回類型來說的.
3.6 Lambda表達(dá)式
3.6.1 Lambda表達(dá)式的簡述
在之前的版本的沒有l(wèi)ambda表達(dá)式,那么要聲明一個委托必須使用一個命名方法,后來出現(xiàn)了匿名方法,不過在后來匿名方法被lambda取代,來作為內(nèi)聯(lián)代碼的首選項(xiàng).但是這并不能說明匿名表達(dá)式就比lambda差,還有一些功能是lambda表達(dá)式很難完成的比如可以使用匿名表達(dá)式來忽略參數(shù)(給參數(shù)指定默認(rèn)值).也就是說匿名方法可以轉(zhuǎn)化為多種簽名的委托.這個是一個很強(qiáng)大的功能,你可以定義一個方法簽名的時候給默認(rèn)值,那么這個方法可以適應(yīng)很多委托,這將減少很多代碼.不過這里不討論僅僅說說而已.有興趣的可以去看看關(guān)于匿名方法.
Lambda表達(dá)式其實(shí)也是一種匿名函數(shù).可以用于創(chuàng)建委托或者表達(dá)式樹目錄類型的一種函數(shù). 通過使用 lambda 表達(dá)式,可以寫入可作為參數(shù)傳遞或作為函數(shù)調(diào)用值返回的本地函數(shù).lambda表達(dá)式的如何定義等可以直接看下面的例子.
下面來一個簡單的例子:
classtestlambda_1
{
publicvoid test()
{
Func<int,string > a = (x) =>
{
Console.WriteLine("this is lambda simple para=" + x);
return"this is lambda return value ";
};
var s = a(100);
Console.WriteLine(s);
}
}
輸出結(jié)果如:
上面的例子中定義了一個返回string的,帶有int輸入?yún)?shù)的委托,使用”=>”符號表示定義一個lambda表達(dá)式,左側(cè)為輸入?yún)?shù),括號中的x就是輸入?yún)?shù),”{}”這個的部分就是函數(shù)的執(zhí)行體.注意”=>”符號是又結(jié)合的運(yùn)算符.上面的這個寫法是不是很簡單呢.用起來也很方便.不過還是有一些需要注意的地方
l 當(dāng)只有一個參數(shù)的時候”()”這個括號是可以省略的(沒有參數(shù)或者更多參數(shù)的時候都是不能夠省略的);例如下面的寫法:Action<string> action=x=>{};
l is或者as運(yùn)算的左側(cè)不允許出現(xiàn)lambda表達(dá)式的.
l 在不容易推算出參數(shù)類型的時候可以在表達(dá)式樹中指定參數(shù)類型.例如(int x,stirng xx)=>{};
l Lambda表達(dá)式主題應(yīng)該是在Framework框架之內(nèi),跨越了這個框架其實(shí)就無意義了.
l 語句 lambda 也不能用于創(chuàng)建表達(dá)式目錄樹.lambda表達(dá)式和語句的區(qū)別在于:表達(dá)式就是一個簡單的語句.沒有用大括號括起來,通常表達(dá)式內(nèi)容大多是幾個判斷語句合在一起.而表達(dá)式語句則可以有大括號括起來內(nèi)容也多很多.其實(shí)這兩個也沒有嚴(yán)格的區(qū)別,就是一個寫法上少了大括號另一個多了大括號.
3.6.2 異步 Lambda
在Framework4.5之后的版本中可以使用async和await關(guān)鍵字來實(shí)現(xiàn)異步lambda.使用起來非常簡單.
例子如下:
classtestLambda_2
{
publicvoid test()
{
Action action =null;
action += async () =>
{
Console.WriteLine("start " + DateTime.Now.ToString());
await test2();
Console.WriteLine("over " + DateTime.Now.ToString());
};
action();
}
asyncTask test2()
{
await Task.Delay(3000);
}
}
這段代碼就是創(chuàng)建一個Action的異步表達(dá)式.通過使用async和await關(guān)鍵字即可完成.這其實(shí)要?dú)w功于async和await關(guān)鍵字的功能,這點(diǎn)本身和lambda表達(dá)式關(guān)系不大.
3.6.3 Lambda的類型推斷
在使用lambda表達(dá)式的時候通常可以不用輸入具體的參數(shù)類型(從上面例子中也可以看出),編譯器會根據(jù)lambda的主體或者參數(shù)的一些其他描述或者上下文來自動推斷參數(shù)或者委托的參數(shù)的類型.比如對于Func<T>這類型的最后一個參數(shù)一般為返回類型.一般的推斷規(guī)則如下:
l Lambda 包含的參數(shù)數(shù)量必須與委托類型包含的參數(shù)數(shù)量相同.
l Lambda 中的每個輸入?yún)?shù)必須都能夠隱式轉(zhuǎn)換為其對應(yīng)的委托參數(shù).
l Lambda 的返回值(如果有)必須能夠隱式轉(zhuǎn)換為委托的返回類型.
注意,lambda 表達(dá)式本身沒有類型,因?yàn)镃#的常規(guī)類型系統(tǒng)沒有“Lambda 表達(dá)式”這個概念.我們常說的lambda表達(dá)式的類型其實(shí)是指委托類型或 lambda 表達(dá)式所轉(zhuǎn)換到的 Expression 類型.
3.6.4 Lambda的變量作用域
定義在lambda函數(shù)方法內(nèi),包括定義在簽名上的參數(shù)和定義在持有l(wèi)ambda表達(dá)式的外部方法中的變量和其他全局變量等都可以被lambda表達(dá)訪問.不過在這里有一個需要特別說明的地方,lambda表達(dá)式在引用lambda表達(dá)式范圍外的變量的時候需要在表達(dá)式內(nèi)進(jìn)行保存,以保證在外部變量超出了范圍并被垃圾回收之后在lambda內(nèi)部還可以使用.
Lambda表達(dá)式的變量范圍和限制如下:
l 捕獲的變量將不會被作為垃圾回收,直至引用變量的委托符合垃圾回收的條件.
l 在外部方法中看不到 lambda 表達(dá)式內(nèi)引入的變量.
l Lambda 表達(dá)式無法從封閉方法中直接捕獲 ref 或 out 參數(shù).
l Lambda 表達(dá)式中的返回語句不會導(dǎo)致封閉方法返回.
l 不能使用goto,break,continue語句來跳出lambda表達(dá)式,同樣也不能夠使用這些語句來直接進(jìn)入lambda表達(dá)式.
例子如下:
classtestLambda_3
{
int count = 0;
publicvoid test()
{
int cc = 101;
Action<string> action = x =>
{
count++;
Console.WriteLine("lambda count=" + count);
Console.WriteLine("para x=" + x);
Console.WriteLine("lambda cc=" + ++cc);
};
action("wo cao");
Console.WriteLine("outer cc=" + ++cc);
Console.WriteLine(" count=" + ++count);
}
}
輸出結(jié)果如下:
3.7事件
3.7.1 普通事件
事件其實(shí)是一個對委托的進(jìn)一部分封裝,可以讓事件的接受者使用更加簡單. 類或?qū)ο罂梢酝ㄟ^事件向其他類或?qū)ο笸ㄖl(fā)生的相關(guān)事情.發(fā)送事件的類稱為“發(fā)行者”,接收事件或者事件處理者的類稱為“訂戶”.事件具有以下特性:
l 發(fā)行者確定何時引發(fā)事件;訂戶確定對事件作出何種響應(yīng).
l 一個事件可以有多個訂戶. 訂戶可以處理來自多個發(fā)行者的多個事件.
l 沒有訂戶的事件永遠(yuǎn)也不會引發(fā).
l 事件通常用于表示用戶操作,例如單擊按鈕或圖形用戶界面中的菜單選項(xiàng).
l 當(dāng)事件具有多個訂戶時,引發(fā)該事件時會同步調(diào)用事件處理程序. 若要異步調(diào)用事件
l 在Framework 的類庫中,事件是基于 EventHandler 委托和 EventArgs 基類來擴(kuò)展或者使用的.
下面給一個簡單的例子來說明事件:
classtestEventA
{
delegatevoidmyEventHandler();
classA :IDisposable
{
//這是一種簡寫的定義方式
publiceventmyEventHandler myEvent;
/*這是完整的定義方法,建議使用下面這種方式
* 在很多時候需要對事件進(jìn)行額外的處理
* 比如在WPF中的隧道事件和冒泡事件
*/
myEventHandler _myEvntt2;
publiceventmyEventHandler myEvntt2
{
add
{
_myEvntt2 +=value;
}
remove
{
_myEvntt2 -=value;
}
}
AutoResetEvent reset =newAutoResetEvent(false);
bool running =false;
public A()
{
reset.Reset();
running = true;
//這里初始化一個定時觸發(fā)兩個事件的線程
Task.Factory.StartNew(() =>
{
while (running)
{
if (myEvent !=null)
{
myEvent();
}
if (_myEvntt2 !=null)
{
_myEvntt2();
}
reset.WaitOne(3000);
}
reset.Dispose();
});
}
publicvoid Dispose()
{
running = false;
reset.Set();
}
}
publicvoid test()
{
A a =newA();
Task.Run(() =>
{
a.myEvntt2 += a_myEvntt2;
a.myEvent += a_myEvent;
a.myEvntt2 += () =>
{
a.myEvntt2 -= a_myEvntt2;
Console.WriteLine("this is innnn asdfasdfasdf");
};
Thread.Sleep(10 * 1000);
a.Dispose();
});
}
void a_myEvent()
{
Console.WriteLine("this is myEvent " + DateTime.Now.ToString());
}
void a_myEvntt2()
{
Console.WriteLine("this is myEvent2 " + DateTime.Now.ToString());
}
}
輸出結(jié)果:
這段代碼有點(diǎn)長,其實(shí)功能很簡單,主要是一個事件發(fā)送者類(A,定義了事件,并且負(fù)責(zé)發(fā)送事件)和一個事件處理代碼.這里用到了事件鏈(其實(shí)就多路委托).從上面的代碼用可以看到定義事件其實(shí)就只需要使用event關(guān)鍵字聲明一個委托類型的屬性或者字段即可.不過推薦使用屬性的方式來聲明事件.事件的訂閱只需使用”+=”,取消訂閱只需要使用”-=”即可.
不過既然有可委托可以完成同樣的工作那么為什么還要有事件呢?其實(shí)這個是從代碼簡寫上來說的.這個就是微軟為大家考慮的屬于一種福利.本質(zhì)上事件就是一種委托.
我們來看看事件內(nèi)部是什么樣子的呢,不過這里有點(diǎn)遺憾沒有找到對應(yīng)的源碼,不過翻遍出來的到時不錯;
剛才的代碼事件其實(shí)在內(nèi)部還是編譯成了屬性,不過上面來個事件的內(nèi)部實(shí)現(xiàn)不一樣哦(一個是字段方式定義,一個屬性方式定義)
我們先來看第一種方式使用字段來直接定義事件的:
public event myEventHandlermyEvent;
這個代碼在編譯之后其實(shí)不是直接使用的委托來合并的.這個通過反編譯的代碼可以看出來:
通過上面的代碼可以看出來,通過該方法定義的屬性需要經(jīng)過很多步驟,甚至需要一個尋來比較和合并.這其實(shí)是比較耗時的.
那么我們來看看通過屬性定義的方式編譯后的代碼會是什么樣子呢:
看到這個代碼是不是覺得比上面的代碼要簡單多了,就一行代碼.
注意不論是聲明為字段還是屬性編譯器都在內(nèi)部編譯成add_和一個 remove_ 這樣的方法.在這些方法內(nèi)部使用的變量都是一個私有變量.在合并委托的時候都是采用了System.Delegate的Combine()靜態(tài)方法,這個方法用于將當(dāng)前的變量添加到委托鏈表中.
這里特別說明幾點(diǎn):
1如果使用匿名函數(shù)訂閱事件,事件的取消訂閱過程將比較麻煩. 這種情況下若要取消訂閱,必須返回到該事件的訂閱代碼,將該匿名方法存儲在委托變量中,然后將此委托添加到該事件中. 一般來說,如果必須在后面的代碼中取消訂閱某個事件,則建議您不要使用匿名函數(shù)訂閱此事件
2 如果事件或者委托采用異步委托,當(dāng)事件或者委托的接收方不唯一的時候必須要先獲取事件或者委托的鏈,然后在逐個發(fā)起異步事件,否則程序?qū)伋鯝rgumentException{"該委托必須有一個目標(biāo)(且僅有一個目標(biāo))."}的異常.
3 事件默認(rèn)情況是線程不安全的.
3.7.2 弱事件
強(qiáng)事件中有一個很不好處理的問題,通過訂閱發(fā)布事件之后直接引用到事件的發(fā)布者上面,這回給垃圾回收器回收此事件帶來麻煩.從而導(dǎo)致內(nèi)存泄漏.因?yàn)閭陕牫绦虿灰玫臅r候,發(fā)布程序還會有一個引用.這個時候垃圾收集器就不能夠回收該對象.針對這種強(qiáng)連接提出一種弱事件的方式來解決這個問題.在發(fā)布程序和偵聽程序之間建立一個中介,這個中介來負(fù)責(zé)管理事件對象的引用.
弱事件出現(xiàn)的目的是為了解決強(qiáng)事件引用在某些情況下導(dǎo)致的內(nèi)存泄漏的問題而出現(xiàn)的.通常來說偵聽程序附加事件處理程序會導(dǎo)致偵聽程序的對象的生存期不確定,該生存期受源的對象生存期影響(除非顯式移除了事件處理程序). 但在某些情況下,可能希望偵聽程序的對象生存期受其他因素(例如對象生存期當(dāng)前是否屬于應(yīng)用程序的可視化樹)控制,而不受源的生存期控制. 如果源對象生存期超出了偵聽器的對象生存期,則常規(guī)事件模式會導(dǎo)致內(nèi)存泄漏:偵聽程序保持活動狀態(tài)的時間比預(yù)期要長.弱事件模式旨在解決此內(nèi)存泄漏問題. 每當(dāng)偵聽程序需要注冊事件,而該偵聽程序并不明確了解什么時候注銷事件時,就可以使用弱事件模式. 當(dāng)源的對象生存期超出偵聽程序的有用對象生存期時,也可以使用弱事件模式.使用弱事件模式,偵聽器可以注冊和接收事件,同時不會對偵聽器的對象生存期特征產(chǎn)生任何影響. 實(shí)際上,來自源的隱含引用無法確定偵聽程序是否適合進(jìn)行垃圾回收. 該引用為弱引用,僅是弱事件模式和相關(guān) API 的命名.可以對偵聽器進(jìn)行垃圾回收或銷毀,在不保留對現(xiàn)在銷毀的對象的非可收集處理程序引用的情況下,源可以繼續(xù).
下面給一個強(qiáng)事件導(dǎo)致的內(nèi)存泄漏的例子(這個例子可能無法完全說明事件導(dǎo)致內(nèi)存泄漏,但至少可以給一個提醒或者演示):
classtestEventOutMemory
{
classEventSender
{
publicstaticint Count = 0;
public EventSender()
{
Interlocked.Add(ref Count, 1);
}
~EventSender()
{
Interlocked.Decrement(ref Count);
}
eventAction _EventA;
publiceventAction EventA
{
add
{
_EventA +=value;
}
remove
{
_EventA -=value;
}
}
publicvoid ShowEvent()
{
if (_EventA !=null)
{
_EventA();
}
}
}
classEventAccept
{
publicstaticint Count = 0;
public EventAccept()
{
Interlocked.Add(ref Count, 1);
}
~EventAccept()
{
Interlocked.Decrement(ref Count);
}
privateList<StringBuilder> sb = newList<StringBuilder>();
publicvoid Do()
{
for (var i = 0; i < 1000;i++)
{
sb.Add(newStringBuilder(10240));
}
}
}
List<EventSender> lst = newList<EventSender>();
publicvoid test()
{
for (int i = 0; i < 100;i++)
{
EventSender s =newEventSender();
s.EventA += newEventAccept().Do;
s.ShowEvent();
lst.Add(s); //這句也注釋掉后觀察進(jìn)程鎖好用的內(nèi)存大小;
}
GC.Collect();
GC.WaitForFullGCComplete();
Console.WriteLine("Sender Count=" + EventSender.Count +",Accept count=" +EventAccept.Count);
}
}
結(jié)果輸出和在任務(wù)管理器中看到的內(nèi)存:
對象數(shù)量:
內(nèi)存耗用:
當(dāng)注釋掉lst.add(s);這句之后的狀態(tài):
從這兩個的對比可以看出內(nèi)存泄漏相當(dāng)嚴(yán)重的.不過呢,其實(shí)大家也沒有必要過多的擔(dān)心,雖然強(qiáng)事件引用會導(dǎo)致內(nèi)存泄漏,但絕大多數(shù)的情況下是不會導(dǎo)致內(nèi)存泄漏的,只需要控制好每個對象的生命周期(不過這個說起來容易,項(xiàng)目做起來后就不容易了).
使用弱事件需要繼承WeekEvenManager類(注意此類在WindowsBase庫里面的System.Windows命名空間中,此類需要單獨(dú)引用),這玩意兒其實(shí)是在wpf中推出的,所以wpf中的很多事件管理都是弱事件.從WeekEventMananger類的繼承層次也可以看出:
下面給出弱事件的使用例子:
classtestWeekEvent
{
classEventSend
{
publiceventAction<object,EventArgs>OnEvent;
publicvoid DoEvent()
{
if (OnEvent !=null)
{
OnEvent(this,newEventArgs());
}
}
}
classEventWeekEvent:WeakEventManager
{
publicstaticvoid AddListener(object source,IWeakEventListenerlistener)
{
CurrentManager.ProtectedAddListener(source, listener);
}
publicstaticvoid RemoveListener(object source,IWeakEventListenerlistener)
{
CurrentManager.ProtectedRemoveListener(source, listener);
}
protectedoverridevoid StartListening(object source)
{
var s = (sourceasEventSend);
if (s !=null)
{
s.OnEvent += s_EventA;
}
}
void s_EventA(object obj, EventArgs e)
{
DeliverEvent(obj, e);
}
protectedoverridevoid StopListening(object source)
{
var s = (sourceasEventSend);
if (s !=null)
{
s.OnEvent -= s_EventA;
}
}
publicstaticEventWeekEvent CurrentManager
{
get
{
EventWeekEvent manger = GetCurrentManager(typeof(EventWeekEvent))asEventWeekEvent;
if (manger ==null)
{
manger =newEventWeekEvent();
SetCurrentManager(typeof(EventWeekEvent), manger);
}
return manger;
}
}
}
classEventAccept:IWeakEventListener
{
privatevoid Execute()
{
Console.WriteLine("OK");
}
publicbool ReceiveWeakEvent(Type managerType, object sender,EventArgse)
{
Execute();
returntrue;
}
}
publicvoid test()
{
EventSend s =newEventSend();
//使用自定義方式的弱事件
EventWeekEvent.AddListener(s,newEventAccept());
//使用泛型方式的弱事件
WeakEventManager<EventSend,EventArgs>.AddHandler(s,"OnEvent",newEventHandler<EventArgs>((o, e) =>
{
Console.WriteLine("fuck OK");
}));
s.DoEvent();
}
}
在這個例子中使用了兩種方式來實(shí)現(xiàn)弱事件.第一種采用的是以前Framework3.0中提供的WeekEventManager類來實(shí)現(xiàn).但在4.5之后微軟提供了一個WeekEventManager類的泛型版本,因此可以直接使用,因此代碼量會減少很多.上面的EventWeekEvent這個類完全是為了實(shí)現(xiàn)弱事件鎖增加的代碼.使用泛型版本的則可以省略這些代碼.
特別注意:
弱事件中事件的參數(shù)必須符合這個標(biāo)準(zhǔn):
1 事件源必須有,且是第一個參數(shù),事件源不能為null.
2 第二個參數(shù)必須是EventArg的子類或者本身;
如果需要更多的內(nèi)容需要自己去擴(kuò)展.這兩個規(guī)范其實(shí)也是Framework中對于事件的一種規(guī)范.在Framework中事件是基于任何有效委托都可以成功,但是推薦使用EventHandler或者EventHandler<TEventArgs>的泛型版本.
3.8觀察者模式
觀察者模式其實(shí)大家見到這個名字就知道是什么意思了.如果還不明白,舉個簡單的例子:比如古代的青樓都有一個老鴇在門口看到有人進(jìn)來就里面去說,哎喲喂,這位管人進(jìn)來玩玩吧我們這里的姑娘可是吹拉彈唱樣樣精通哦.然后就通知姑娘出來接客了.這個過程中其實(shí)就是一個典型的觀察者模式(中國古人很智慧的).老鴇就一個事件發(fā)起者,姑娘就是事件接收者.而這個客人就是一個被監(jiān)視的對象.
觀察者模式的定義: Observer設(shè)計模式是為了定義對象間的一種一對多的依賴關(guān)系,以便于當(dāng)一個對象的狀態(tài)改變時,其他依賴于它的對象會被自動告知并更新.Observer模式是一種松耦合的設(shè)計模式.
下面給出例子:
classtestObserver
{
classlaobao
{
eventAction<string> _CustomerCome;
publiceventAction<string> CustomCome
{
add
{
_CustomerCome +=value;
}
remove
{
_CustomerCome -=value;
}
}
public laobao()
{
Task.Run(() =>
{
Thread.Sleep(1000);
Console.WriteLine("門口出現(xiàn)了客人,老鴇發(fā)現(xiàn)了");
if (_CustomerCome !=null)
{
_CustomerCome("姑娘們出來接客了...");
}
});
}
}
classmein1
{
publicvoid AcceptCustom(string cmd)
{
Console.WriteLine("美女 1 收到老鴇的接客通:" + cmd);
Console.WriteLine("美女 1 從房間里出來接客");
}
}
classmein2
{
publicvoid AcceptCustom(string cmd)
{
Console.WriteLine("美女 2 收到老鴇的接客通:" + cmd);
Console.WriteLine("美女 2 從房間里出來接客");
}
}
classmein3
{
publicvoid AcceptCustom(string cmd)
{
Console.WriteLine("美女 3 收到老鴇的接客通:" + cmd);
Console.WriteLine("美女 3 從房間里出來接客");
}
}
publicvoid test()
{
laobao _m =newlaobao();
mein1 m1 =newmein1();
mein2 m2 =newmein2();
mein3 m3 =newmein3();
_m.CustomCome += m1.AcceptCustom;
_m.CustomCome += m2.AcceptCustom;
_m.CustomCome += m3.AcceptCustom;
}
}
輸出結(jié)果:
這個就是一個典型的觀察者模式.當(dāng)然觀察者模式應(yīng)用相當(dāng)廣泛.傳統(tǒng)的跨服務(wù)器的消息通知其實(shí)都是基于這種思想來實(shí)現(xiàn)的.
文章pdf下載地址
http://download.csdn.net/detail/shibinysy/9742961
新聞熱點(diǎn)
疑難解答