▲簡單委托的構成:
可以選擇將委托類型看做只定義了一個方法的接口,將委托的實例看做實現了那個接口的一個對象。
1. 聲明委托類型——定義委托
混亂的根源:容易產生歧義的“委托”
委托經常被人誤解,這是由於大家喜歡用委托這個詞來描述委托類型和委托實例。
Console.WriteLine("StringProcessor父類:" + typeof(StringProcessor).BaseType);//StringProcessor父類:System.MulticastDelegate
委托的基類:System.MulticastDelegate——的基類:System.Delegate
2. 為委托實例的操作找到一個恰當的方法
3、創建委托實例
需要一個方法以及(對於實例方法來說)調用方法的目標;
單純創建一個委托實例卻不在某一時刻調用它是沒有什么意義的。看看最后一步——調用。
4. 調用委托實例
“委托實例被調用”中的“調用”對應的是invoke。
invoke理解為“喚出”某個東西來幫你調用一個信息不明的方法時,用invoke比call恰當。理解為喚出和調用區別不明顯。
調用委托實例的兩種方式——顯式調用Invoke和使用C#的簡化形式。一般情況下只需使用簡化形式。
- 聲明的委托實例.invoke(參數)
- 聲明的委托實例(參數)
委托的實質是間接完成某種操作,這增大了復雜性(看看為了輸出這點兒內容,用了多少行代碼),但同時也增加了靈活性。
5.合並和刪除委托
System.Delegate類型的靜態方法Combine和Remove負責創建新的委托實例。
其中, Combine等價於+=,而Remove等價於-=
委托是不易變的:
委托實例類似於string,創建了委托實例后,有關他的一切就不能改變。
事件和委托的用途
委托delegate:
用途最廣的是,使用委托為形參,傳遞實參(回調函數)時,可以使用匿名函數。對比Java:java使用匿名函數是形參是接口,創建一個接口實現類匿名類,匿名類實現接口方法。
例如:
var t4 = Task.Run(() => TaskMethod.DoTask("using Run method")); //系統方法Run 就使用了委托作為參數 public static Task<TResult> Run<[NullableAttribute(2)] TResult>(Func<TResult> function);
事件event:
開發者經常將事件和委托實例,或者將事件和委托類型的字段混為一談。之所以產生混淆,原因和以前相同,因為C#提供了一種簡寫方式,允許使用字段風格的事件( field-like event)
字段風格的事件使所有這些的實現變得更易閱讀,只需一個聲明就可以了。 編譯器會將聲明轉換成一個具有默認add/remove實現的事件和一個私有委托類型的字段。
事件不是委托實例——只是成對的add/remove方法(類似於屬性的取值方法/賦值方法)事件包含一個私有委托類型字段。
void OnEventRaised(object sender, EventArgs args);
發布器類:
- 聲明委托,事件委托一般命名為:NameEventHandler
- 聲明事件
//event關鍵字代表事件,返回類型為委托; public event BoilerLogHandler BoilerEventLog;//基於委托定義事件,委托的函數指針。
- 創建引發事件的方法,一般命名為:OnEventName。
訂閱器類
- 實例化發布器類
- 綁定事件:+= 委托(或函數都可以)
發布器類一個方法OnEventName() 等價於,訂閱事件中的所有綁定方法一起執行。(廣播)
public class Host { //定義委托原型 public delegate void OpenDoorEventHandler(); //定義委托類型的事件 public event GoHomeEventHandler OpenDoor; //定義內部一個方法,在這個方法內判斷,OpenDoor事件是否被其他對象注冊,一旦注冊了,則調用執行事件。 protected void OnOpenDoor() { if(OpenDoor!=null) { OpenDoor(); } } public void GoHome() { OnOpenDoor(); } } public class Cat { public void Run() { //貓跑了 } } public class Dog { public void Bark() { //狗叫了 } } 如何使用如下: Host 主人 =new Host(); Cat 貓 = new Cat(); Dog 狗 = new Dog(); 主人.OpenDoor += new 主人.OpenDoorEventHandler(貓.Run); 主人.OpenDoor += new 主人.OpenDoorEventHandler(狗.Back); 主人.GoHome【發布器類】就等價於 貓.Run();//【訂閱器類】 狗.Back();//【訂閱器類】
當被觀察者【發布器類】,做出某一特定“動作”(被觀察者【發布器類】的特定“動作”【發布器類中引發事件的方法,一般命名為:OnEventName】,注冊了N個不同對象的不同反映【訂閱器類】),觀察者【訂閱器類】對這個特定“動作”做出不懂的反映。
2.2 事件用法三步曲
事件機制的使用方法可以歸納為3個步驟:
(1)事件發布者定義event以及事件相關信息 (2)事件偵聽者訂閱event (3)事件發布者觸發event,自動調用訂閱者的事件處理方法。
到這里可能有的小伙伴覺得 和 多播委托 一樣,添加也是+=,取消也是-=
其實事件就是多播委托的一種封裝,如果不使用事件,直接使用委托,能不能實現事件機制呢?答案是完全可以。
那么,為什么還要用事件呢?事件是怎么封裝了委托呢?
實際上,在以上案例中,使用 event 關鍵字在一行上定義事件是C# 提供的事件的簡化記法。在我們定義了上述事件后,編譯器會自動生成如下代碼:
private EventHandler<CarInfoEventArgs> newCarEvent; public event EventHandler<CarInfoEventArgs> NewCarEvent { add { newCarEvent += value; } remove { newCarEvent -= value; } }
這非常像字段及其屬性的關系。注意到,委托字段 newCarEvent 是私有的,因此在外部不能直接為事件賦值,但可以通過公開的 += 和 -= 運算符為事件添加實例方法。 另外,事件 event 是一種數據類型,是一個已經聲明的委托類,只能在某個類的內部聲明,並且只能被該類調用,不能在命名空間中聲明和使用。這一點與委托 delegate 的聲明不同。
總結:事件與委托的區別在於兩點:
(1)委托是一個類,可以在命名空間中聲明;而事件只能在事件發布者內部定義,且只能被該類調用。
(2)可以直接使用一個方法為委托賦值,而事件只開放了 += 和 -= 運算符為其添加或刪除方法。
————————————————
版權聲明:本文為CSDN博主「wnvalentin」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/wnvalentin/article/details/82254656