委托與事件在C#1.0的時候就有了,隨著C#版本的不斷更新,有些寫法和功能也在不斷改變,本文溫故一下這些改變,以及在NET Core中關于事件的一點改變,
一、C#1.0 從委托開始
1. 基本方式
什么是委托,就不說概念了,用例子說話,
某HR說他需要招聘一個6年 .NET5 研發經驗的“高級”工程師,他想找人(委托)別人把這條招聘訊息發出去,這樣的HR很多,所以大家定義了一個通用的發訊息規則:
public delegate string SendDelegate(string message);
這就像一個介面的方法,沒有實際的實作代碼,只是定義了這個方法有一個string的引數和回傳值,所有想發招聘訊息的HR只要遵守這樣的規則即可,
委托本質上是一個類,所以它可以被定義在其他類的內部或外部,根據實際參考關系考慮即可,本例單獨定義在外部,
為HR定義了一個名為HR的類:
public class HR { public SendDelegate sendDelegate; public void SendMessage(string msg) { sendDelegate(msg); } }
HR有一個SendDelegate型別的成員,當它需要發送訊息(SendMessage)的時候,只需要呼叫這個sendDelegate方法即可,而不需要實作這個方法,也不需要關心這個方法是怎么實作的,
當知道這個HR需要發送訊息的時候,獵頭張三接了這個幫忙招人的作業,獵頭的類為Sender,他有一個用于發送訊息的方法Send,該方法恰好符合眾人定義的名為SendDelegate的發訊息規則,這有點像實作了一個介面方法,但這里不要求方法名一致,只是要求方法的簽名一致,
public class Sender { public Sender(string name) { this.senderName = name; } private readonly string senderName; public string Send(string message) { string serialNumber = Guid.NewGuid().ToString(); Console.WriteLine(senderName + " sending...."); Thread.Sleep(2000); Console.WriteLine("Sender: " + senderName + " , Content: " + message + ", Serial Number: " + serialNumber); return serialNumber; } }
獵頭幫助HR招人的邏輯如下:
public void Test() { //一個HR HR hr = new HR(); //獵頭張三來監聽,聽到HR發什么訊息后立刻傳播出去 Sender senderZS = new Sender("張三"); hr.sendDelegate = senderZS.Send;
//HR遞交訊息 hr.SendMessage("Hello World"); }
獵頭將自己的發訊息方法“賦值”給了HR的SendDelegate方法,為什么可以“賦值”? 因為二者都遵守SendDelegate規則, 就像A和B兩個變數都是int型別的時候,A可以賦值給B一樣,
這就是一個簡單的委托程序,HR將招人的作業委托給了獵頭,自己不用去做招人的作業,
但經常一個招聘作業經常會有多個獵頭接單,那就有了多播委托,
2. 多播委托
看一下下面的代碼:
public void Test() { //一個HR HR hr = new HR(); //獵頭張三來監聽,聽到HR發什么訊息后立刻傳播出去 Sender senderZS = new Sender("張三"); hr.sendDelegate = senderZS.Send; //快嘴李四也來了 Sender senderLS = new Sender("李四"); hr.sendDelegate += senderLS.Send;
//HR遞交訊息 hr.SendMessage("Hello World"); }
與之前的代碼改變不大, 只是添加了李四的方法系結,這樣HR發訊息的時候,張三和李四都會發出招人的訊息,
這里要注意李四系結方法的時候,用的是+=而不是=,就像拼接字串一樣,是拼接而不是賦值,否則會覆寫掉之前張三的方法系結,
對于第一個系結的張三,可以用=號也可以用+=(記得之前好像第一個必須用=,實驗了一下現在二者皆可),
這同時也暴露了一些問題:
- 如果后面的獵頭接單的時候不小心(故意)用了=號, 那么最終前面的人的系結都沒有了,那么他將獨占這個HR客戶,HR發出的訊息只有他能收到,
- 可以偷偷的呼叫獵頭的hr.sendDelegate
public void Test() { //一個HR HR hr = new HR(); //大嘴張三來監聽,聽到HR發什么訊息后立刻傳播出去 Sender senderZS = new Sender("張三"); //hr.sendDelegate -= senderZS.Send; //即使未進行過+= 直接呼叫-=,也不會報錯 hr.sendDelegate += senderZS.Send; //快嘴李四也來了 Sender senderLS = new Sender("李四"); hr.sendDelegate += senderLS.Send; //移除 //hr.sendDelegate -= senderZS.Send; //風險:注意上面用的符號是+=和-= 如果使用=,則是賦值操作, //例如下面的陳述句會覆寫掉之前所有的系結 //hr.sendDelegate = senderWW.Send; //HR遞交訊息 hr.SendMessage("Hello World"); //風險:可以偷偷的以HR的名義偷偷的發了一條訊息 sendDelegate應該只能由HR呼叫 hr.sendDelegate("偷偷的發一條"); }
3. 通過方法避免風險
很自然想到采用類似Get和Set的方式避免上面的問題,既然委托可以像變數一樣賦值,那么也可以通過引數來傳值,將一個方法作為引數傳遞,
public class HRWithAddRemove { private SendDelegate sendDelegate; public void AddDelegate(SendDelegate sendDelegate) { this.sendDelegate += sendDelegate; //如果需要限制最多系結一個,此處可以用=號 } public void RomoveDelegate(SendDelegate sendDelegate) { this.sendDelegate -= sendDelegate; } public void SendMessage(string msg) { sendDelegate(msg); } }
經過改造后的HR,SendDelegate方法被設定為了private,之后只能通過Add和Remove的方法進行方法系結,
4.模擬多播委托機制
通過上面委托的表現來看,委托就像是保存了一個相同方法名的集合 List<SendDelegate> ,可以向集合中添加或移除方法,當呼叫這個委托的時候,會逐一呼叫該集合中的各個方法,
例如下面的代碼( 注意這里假設SendDelegate只對應一個方法 ):
public class HR1 { public void Delegate(SendDelegate sendDelegate) { sendDelegateList = new List<SendDelegate> { sendDelegate }; //對應= } public void AddDelegate(SendDelegate sendDelegate) { sendDelegateList.Add(sendDelegate); //對應+= } public void RomoveDelegate(SendDelegate sendDelegate) { sendDelegateList.Remove(sendDelegate);//對應-= } public List<SendDelegate> sendDelegateList; public void SendMessage(string msg) { foreach (var item in sendDelegateList) { item(msg); } } }
二、C#1.0 引入事件
1.簡單事件
如果既想使用-=和+=的方便,又想避免相關功能開閉的風險怎么辦呢?可以使用事件:
public class HRWithEvent { public event SendDelegate sendDelegate; public void SendMessage(string msg) { sendDelegate(msg); } }
只是將SendDelegate前面添加了一個event標識,雖然它被設定為public,但如下代碼卻會給出錯誤提示: 事件“HRWithEvent.sendDelegate”只能出現在 += 或 -= 的左邊(從型別“HRWithEvent”中使用時除外)
hr.sendDelegate = senderZS.Send; hr.sendDelegate("偷偷的發一條");
2.事件的訪問器模式
上文為委托定義了Add和Remove方法,而事件支持這樣的訪問器模式,例如如下代碼:
public class CustomerWithEventAddRemove { private event SendDelegate sendDelegate; public event SendDelegate SendDelegate { add { sendDelegate += value; } remove { sendDelegate -= value; } } public void SendMessage(string msg) { sendDelegate(msg); } }
可以像使用Get和Set方法一樣,對事件的系結與移除進行條件約束,
3. 控制系結事件的執行
當多個委托被系結到事件之后,如果想精確控制各個委托的運行怎么辦,比如回傳值(雖然經常為void)、例外處理等,
第一章第4節通過一個List<SendDelegate> 模擬了多播委托的系結, 會想到如果真能回圈呼叫一個個已系結的委托,就可以精確的進行控制了,那么這里說一下這樣的方法:
public class HRWithEvent { public event SendDelegate sendDelegate; public void SendMessage(string msg) { //sendDelegate(msg); 此處不再一次性呼叫所有 if (sendDelegate != null) { Delegate[] delegates = sendDelegate.GetInvocationList(); //獲取所有已系結的委托 foreach (var item in delegates) { ((SendDelegate)item).Invoke(msg); //逐一呼叫 } } } }
這里通過Invoke方法逐一呼叫各個Delegate,從而實作對每一個Delegate的呼叫的控制,若需要異步呼叫,則可以通過BeginInvoke方法實作(.NET Core之后不再支持此方法,后面會介紹,)
((SendDelegate)item).BeginInvoke(msg,null,null);
4. 標準的事件寫法
.NET 事件委托的標準簽名是:
void OnEventRaised(object sender, EventArgs args);
回傳型別為 void, 事件基于委托,而且是多播委托, 引數串列包含兩種引數:發件人和事件引數, sender 的編譯時型別為 System.Object,
第二種引數通常是派生自 System.EventArgs 的型別(.NET Core 中已不強制要求繼承自System.EventArgs,后面會說到),
將上面的例子修改一下,改成標準寫法,大概是下面代碼的樣子:
public class HRWithEventStandard { public delegate void SendEventHandler(object sender, SendMsgArgs e); public event SendEventHandler Send; public void SendMessage(string msg) { var arg = new SendMsgArgs(msg); Send(this,arg); //arg.CancelRequested 為最后一個的值 因為覆寫 } } public class SendMsgArgs : EventArgs { public readonly string Msg = string.Empty; public bool CancelRequested { get; set; } public SendMsgArgs(string msg) { this.Msg = msg; } }
三、隨著C#版本改變
1. C#2.0 泛型委托
C#2.0 的時候,隨著泛型出現,支持了泛型委托,例如,在委托的簽名中可以使用泛型,例如下面代碼
public delegate string SendDelegate<T>(T message);
這樣的委托適用于不同的引數型別,例如如下代碼(注意使用的時候要對應具體的型別)
public delegate string SendDelegate<T>(T message); public class HR1 { public SendDelegate<string> sendDelegate1; public SendDelegate<int> sendDelegate2; public SendDelegate<DateTime> sendDelegate3; } public static class Sender1 { public static string Send1(string msg) { return ""; } public static string Send2(int msg) { return ""; } } public class Test { public void TestDemo() { HR1 hr1 = new HR1(); hr1.sendDelegate1 = Sender1.Send1; // 注意使用的時候要對應具體的型別 hr1.sendDelegate2 = new SendDelegate<int>(Sender1.Send2); hr1.sendDelegate3 = delegate (DateTime dateTime) { return dateTime.ToLongDateString(); }; } }
2. C#2.0 delegate運算子
delegate 運算子創建一個可以轉換為委托型別的匿名方法:
例如上例中這樣的代碼:
hr1.sendDelegate3 = delegate (DateTime dateTime) { return dateTime.ToLongDateString(); };
3. C#3.0 Lambda 運算式
從 C# 3 開始,lambda 運算式提供了一種更簡潔和富有表現力的方式來創建匿名函式, 使用 => 運算子構造 lambda 運算式,
例如“delegate運算子”的例子可以簡化為如下代碼:
hr1.sendDelegate3 = (dateTime) => { return dateTime.ToLongDateString(); };
4.C#3,NET Framework3.5,Action 、Func、Predicate
Action 、Func、Predicate本質上是框架為我們預定義的委托,在上面的例子中,我們使用委托的時候,首先要定義一個委托型別,然后在實際使用的地方使用,而使用委托只要求方法名相同,在泛型委托出現之后,“定義委托”這一操作就顯得越來越累贅,為此,系統為我們預定義了一系列的委托,我們只要使用即可,
例如Action的代碼如下:
public delegate void Action(); public delegate void Action<in T>(T obj); public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2); public delegate void Action<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3); public delegate void Action<in T1, in T2, in T3, in T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4); public delegate void Action<in T1, in T2, in T3, in T4, in T5>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9); public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10); public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11); public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12); public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13); public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14); public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15); public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
實際上定義了最多16個引數的無回傳值的委托,
Func與此類似,是最多16個引數的有回傳值的委托,Predicate則是固定一個引數以及bool型別回傳值的委托,
public delegate bool Predicate<T>(T obj);
5. .NET Core 異步呼叫
第2.3節中,提示如下代碼在.NET Core中已不支持
((SendDelegate)item).BeginInvoke(msg,null,null);
會拋出例外:
System.PlatformNotSupportedException:“Operation is not supported on this platform.”
需要異步呼叫的時候可以采用如下寫法:
Task task = Task.Run(() => ((SendDelegate)item).Invoke(msg));
對應的 EndInvoke() 則改為: task.Wait();
5. .NET Core的 EventHandler<TEventArgs>
.NET Core 版本中,EventHandler<TEventArgs> 定義不再要求 TEventArgs 必須是派生自 System.EventArgs 的類, 使我們使用起來更為靈活,
例如我們可以有這樣的寫法:
EventHandler<string> SendNew
這在以前的版本中是不允許的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/4879.html
標籤:.NET Core
上一篇:[視頻]iNeuOS 自主可控工業互聯網一體化解決方案 整體介紹
下一篇:(1)RabbitMQ簡介與安裝
