深刻理解:C#中的委托、事件
?
C#中的事件還真是有點繞啊,以前用JavaScript的我,理解起來還真是廢了好大勁!剛開始還真有點想不明白為什么這么繞,想想和JS的區別,最后終于恍然大悟!
C#中事件繞的根本原因:
C#的方法,它不是一個類型,它只是其它類型的成員;
C#是一個強類型的語言,定義方法時,它的參數必須指定類型,如public void add(int n){...};
所以,一個方法不能直接作為其它方法的參數,把一個方法名作為參數,無法指定類型啊,會報錯!那我就想啊,既然不能直接傳入,那我傳入整個對象總可以吧,通過傳進來的對象來執行該方法,如下代碼:
using System;namespace MyEventTest{ ??public class SomeClass{ ? ? ?
??public void Start(int a)
{ Console.WriteLine("Go:{0}",a); }} ?
??
?? ?public class Publisher{ ? ? ?
?? ? ? ? ?public void StartEvent(int a, SomeClass sc) ? ? ? ?{ ? ? ? ? ? ?if (sc != null){sc.Start(a); ?//觸發回調方法}}} ?
???
????public class MainClass{ ? ? ? ?static void Main() ? ? ? ?{SomeClass some = new SomeClass();Publisher p = new Publisher();p.StartEvent(5,some); //Go:5}} }
以上方法確實可以,但C#不完全是這樣實現事件的,因為方法的特殊性,C#引入了委托的概念,讓委托對象來代表方法作為其它方法的參數;而事件對象,其實就是一個委托對象。下面先介紹一下委托:
委托
對應于以上方法:
public void Start(int a) { Console.WriteLine("Go:{0}",a); }
我們可以定義一個委托類型:
public delegate void MyDel(int a);
委托的定義:
委托MyDel它是一個類型,類型名就是MyDel;定義委托相當于定義一個新類,委托在后臺實現為派生自System.Delegate類。
定義委托,就是告訴編譯器該委托將表示哪種方法(返回值類型+方法簽名),該方法可以是任意類型的實例方法、靜態方法,只要方法的簽名、返回值類型與委托匹配,那么該委托的實例就可以引用這些方法。
使用委托,必須創建該委托的實例,并為它指定要引用的方法,如:MyDel d = some.Start;注意這里不是some.Start();
委托對象支持"+","+="來為它添加更多的方法引用,而"-","-="則是刪除引用;
引用了多個方法的委托就叫多播委托,多播委托派生自基類System.MulticastDelegate類,它是System.Delegate類的子類
注意:只要委托對象還存在對方法的引用,它就一直占用內存哦!我想可以用d = null;來釋放委托對象d;
可以對委托對象執行調用,如:d(5);它將把調用傳遞給它所引用的方法some.Start(5);,對于多播委托,它將按順序調用它引用的所有方法,但如果其中一個方法拋出異常,且沒在方法內部處理,則將會將異常往外拋出,之后的方法調用將終止。
使用委托的規則:
委托是和類一個級別的,可以在能定義類的任何地方定義委托;
委托不支持繼承;
可以為委托類型定義任意常見的訪問修飾符;
委托對象所引用的方法也可以是匿名方法、Lambda 表達式;
多播委托的返回值類型必須是void,否則就只能得到委托調用的最后一個方法的結果。
在.NET 4.0中,委托開始支持協變與逆變,這樣一來,定義委托類型時的簽名可以和所要引用的方法的簽名不完全匹配(不同類型之間必須是派生關系)
委托支持泛型,.NET預定義了兩個泛型版本的委托:
Action< T >委托表示引用一個返回值類型為void的方法,根據參數個數存在不同的變體版本;如:Action<in T1, in T2>
Func< T >委托表示引用一個帶返回值類型的方法,根據參數個數存在不同的變體版本;如:Func<in T1, out TResult>1個參數T1和返回值類型TResult。
事件
說完了委托的概念,就可以繼續講事件了,因為事件是基于委托的!
事件的概念:
類或對象可以通過事件向其他類或對象通知發生的相關事情。
發送事件的類稱為“發行者”,接收事件的類稱為“訂閱者”。(就是設計模式中的訂閱發布者模式);
一個事件可以有多個訂閱者。 一個訂閱者可處理來自多個發行者的多個事件。如果一個事件有多個訂閱者,當引發該事件時,會同步調用多個事件處理程序。也可異步調用。
.NET Framework 類庫中的所有事件均基于?EventHandler?委托,還有泛型版本EventHandler<EventArgs>,這個委托是.NET預定義的,不需要我們定義,可以直接用它來實例化一個事件對象,定義如下:
參數object sender對象是對發布者的實例的引用,EventArgs e對象主要用來存儲事件數據
public delegate void EventHandler(object sender, EventArgs e); //EventArgs主要用來存儲事件數據
public delegate void EventHandler<TEventArgs>(object sender, EventArgs e);
雖然在自定義的類中的事件可基于任何有效委托類型,但是,通常建議使用.NET預定義事件委托類型讓事件基于 .NET 標準事件模式
下面是我總結的發布基于 .NET 標準事件模式的4個步驟:
第1步:在發布者類中實例化委托事件,并定義一個實例方法,用來調用委托事件(因為委托事件只能通過定義它的類的實例來調用)。
定義發布者類之前可先定義一個用來存儲事件數據的類(它必須派生于EventArgs基類),如下:
注意:在方法StartEvent()中,聲明了一個變量,來保存事件對象的副本,這樣在取得事件對象的副本后,到觸發事件時,這段時間內,這個事件副本就不會受其它線程的影響。如:在此期間,其它線程注銷了回調方法,那么MyEvent就為null了,這時再觸發事件將引發錯誤。(這就是線程安全的事件,當然還可以通過鎖機制,或者為事件對象始終引用一個空方法)
public class MyEventArgs: EventArgs ?//定義存儲事件數據的類{ ??public int Current{get;set;} }
?
?public class Publisher{ ?
?public event EventHandler<MyEventArgs> MyEvent; //第1步:實例化委托事件public int Sum{get;set;}
? ?public void StartEvent(int a) ? ? ?{ ? ? ?
? ? ? ?var EventCopy = MyEvent; //每次都取一個副本MyEventArgs args = new MyEventArgs();args.Current = a; ? ?
? ? ? ?? ? ?this.Sum += a; ? ? ? ? ?if (EventCopy != null){EventCopy(this,args); ?//調用事件}} }
第2步:定義訂閱者類,在該類中定義和委托事件相匹配的方法(事件觸發時,實際要執行的方法)
public class Subscriber{ ? ?public void Dosomething1(object obj, MyEventArgs e) ? ? {Publisher p = (Publisher)obj;Console.WriteLine("Meg: Sum = {0}, Current = {1}", p.sum, e.Current);} ? ? public void Dosomething2(object obj, MyEventArgs e) ? ? {} }
第3步:在客戶端代碼中,在發布者類的實例上為委托事件注冊回調方法
public class MainClass {static void Main(){Publisher p = new Publisher{ Sum = 0 };Subscriber sub = new Subscriber();p.MyEvent += sub. Dosomething1; ?//注冊回調方法p.MyEvent += sub. Dosomething2;p. StartEvent( 5 ); //調用方法,間接觸發事件p.MyEvent -= sub. Dosomething1; ?//取消注冊} }要點:事件對象其實就是一個委托對象,把事件當委托來看,就比較容易理解了!不要被Event這個單詞給蒙蔽了!
介紹完了!下回將介紹C#中的其它一些較難理解的概念!
原文地址:http://www.cnblogs.com/susufufu/p/6160764.html
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的深刻理解:C#中的委托、事件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Mono新突破:CentOS 7.2下安
- 下一篇: .Net Core中使用ref和Span