轉(zhuǎn)載請注明出處:??https://blog.csdn.net/weixin_44013533/article/details/134655722
作者:CSDN@|Ringleader|
主要參考:
- 委托(C# 編程指南)
- 事件介紹
- C# 中的委托和事件簡介
- Delegate 類
- Exploring the Observer Design Pattern微軟技術(shù)文章翻譯
一 委托
委托是一種引用類型,表示對具有特定參數(shù)列表和返回類型的方法的引用。以下示例聲明名為 Callback 的委托,表示該委托可以封裝以字符串作為參數(shù)并返回 void 的方法:
public delegate void Callback(string message);
- 委托是一個(gè)類型,意味著與class interface enum 等類型同級別,可以定義在namespace下,也可以在class內(nèi)部。
- 委托類似于 C++ 函數(shù)指針,但是是面向?qū)ο笄沂穷愋桶踩摹?
- 因?yàn)槲惺桥c特定方法簽名相關(guān)聯(lián)的類型,這意味著編譯器會(huì)在編譯時(shí)檢查委托與方法之間的匹配性,確保委托只能引用與其聲明的簽名相匹配的方法。這有助于在編譯階段捕獲一些類型不匹配的錯(cuò)誤。
- 與函數(shù)指針不同,委托面向?qū)ο笄翌愋桶踩?。而且委托可以表示靜態(tài)方法或?qū)嵗椒ǎ?當(dāng)委托表示實(shí)例方法時(shí),委托不僅存儲(chǔ)對方法入口點(diǎn)的引用,還存儲(chǔ)對類實(shí)例的引用。
- 委托允許將方法作為參數(shù)進(jìn)行傳遞,因此便于實(shí)現(xiàn)方法回調(diào)。
- 多個(gè)相同簽名的委托實(shí)例可以鏈接在一起,invoke時(shí)實(shí)現(xiàn)多個(gè)方法的順序調(diào)用,也就是委托的多播。
1.1 委托的使用
一個(gè)完整示例:
// Declare a delegate.
delegate void NotifyCallback(string str);
// Declare a method with the same signature as the delegate.
static void Notify(string name)
{
Console.WriteLine($"Notification received for: {name}");
}
public static void Main(string[] args)
{
// Create an instance of the delegate.
NotifyCallback del= new NotifyCallback(Notify);
// Call the delegate.
del("Hello World");
}
其中委托實(shí)例化除了使用NotifyCallback callback= new NotifyCallback(Notify);
也可以使用下面方法:
// 1. 將方法組分配給委托類型:
// C# 2.0 provides a simpler way to declare an instance of NotifyCallback.
NotifyCallback del2 = Notify;
// 2. 聲明匿名方法:
// Instantiate NotifyCallback by using an anonymous method.
NotifyCallback del3 = delegate(string name)
{ Console.WriteLine($"Notification received for: {name}"); };
// 3. 使用 lambda 表達(dá)式:
// Instantiate NotifyCallback by using a lambda expression.
NotifyCallback del4 = name => { Console.WriteLine($"Notification received for: {name}"); };
其中委托的調(diào)用除了使用 del("Hello World");
也可以使用del.Invoke("Hello World")
.
需要注意的是,如果委托實(shí)例沒有綁定任何方法,調(diào)用委托會(huì)報(bào)'System.NullReferenceException'
異常,Invoke
寫法可以使用Null 條件運(yùn)算符 ?
判空,如:del?.Invoke("Hello World")
,而括號寫法不行。
參見: null 條件運(yùn)算符 ?. 和 ?[]
1.2 異步調(diào)用委托
上面的括號或者Invoke調(diào)用委托形式都是同步調(diào)用,除此之外,還有BeginInvoke
的異步調(diào)用形式。
BeginInvoke
方法啟動(dòng)異步調(diào)用。 該方法具有與你要異步執(zhí)行的方法相同的參數(shù),另加兩個(gè)可選參數(shù)。 第一個(gè)參數(shù)是一個(gè) AsyncCallback 委托,此委托引用在異步調(diào)用完成時(shí)要調(diào)用的方法。 第二個(gè)參數(shù)是一個(gè)用戶定義的對象,該對象將信息傳遞到回調(diào)方法。 BeginInvoke 將立即返回,而不會(huì)等待異步調(diào)用完成。 BeginInvoke 返回可用于監(jiān)視異步調(diào)用的進(jìn)度的 IAsyncResult。
EndInvoke
方法用于檢索異步調(diào)用的結(jié)果。 它可以在調(diào)用 BeginInvoke之后的任意時(shí)間調(diào)用。 如果異步調(diào)用尚未完成,那么 EndInvoke 將阻止調(diào)用線程,直到完成異步調(diào)用。 EndInvoke 的參數(shù)包括要異步執(zhí)行的方法的 out 和 ref 參數(shù)以及 BeginInvoke 返回的 IAsyncResult。
下面的代碼示例演示了異步調(diào)用同一個(gè)長時(shí)間運(yùn)行的方法 TestMethod的各種方式。 TestMethod 方法會(huì)顯示一條控制臺(tái)消息,說明該方法已開始處理,休眠了幾秒鐘,然后結(jié)束。 TestMethod 有一個(gè) out 參數(shù),該參數(shù)用于演示將此類參數(shù)添加到 BeginInvoke 和 EndInvoke的簽名中的方式。
- 下面的代碼示例顯示了 TestMethod 的定義和可用于異步調(diào)用 AsyncMethodCaller 的名稱為 TestMethod 的委托。 要編譯此代碼示例,必須包括 TestMethod 和 AsyncMethodCaller 委托的定義。
using System;
using System.Threading;
namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class AsyncDemo
{
// The method to be executed asynchronously.
public string TestMethod(int callDuration, out int threadId)
{
Console.WriteLine("Test method begins.");
Thread.Sleep(callDuration);
threadId = Thread.CurrentThread.ManagedThreadId;
return String.Format("My call time was {0}.", callDuration.ToString());
}
}
// The delegate must have the same signature as the method
// it will call asynchronously.
public delegate string AsyncMethodCaller(int callDuration, out int threadId);
}
- 異步執(zhí)行方法的最簡單方式是通過調(diào)用委托的 BeginInvoke 方法開始執(zhí)行此方法,在主線程上執(zhí)行一些操作,然后調(diào)用委托的 EndInvoke 方法。 EndInvoke 可能會(huì)阻止調(diào)用線程,因?yàn)樵摲椒ㄖ钡疆惒秸{(diào)用完成后才返回。 這種方式非常適合執(zhí)行文件或網(wǎng)絡(luò)操作。
using System;
using System.Threading;
namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class AsyncMain
{
public static void Main()
{
// The asynchronous method puts the thread id here.
int threadId;
// Create an instance of the test class.
AsyncDemo ad = new AsyncDemo();
// Create the delegate.
AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);
// Initiate the asynchronous call.
IAsyncResult result = caller.BeginInvoke(3000,
out threadId, null, null);
Thread.Sleep(0);
Console.WriteLine("Main thread {0} does some work.",
Thread.CurrentThread.ManagedThreadId);
// Call EndInvoke to wait for the asynchronous call to complete,
// and to retrieve the results.
string returnValue = caller.EndInvoke(out threadId, result);
Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".",
threadId, returnValue);
}
}
}
/* This example produces output similar to the following:
Main thread 1 does some work.
Test method begins.
The call executed on thread 3, with return value "My call time was 3000.".
*/
異步委托其他內(nèi)容參見:使用異步方式調(diào)用同步方法
1.3 委托多播
調(diào)用時(shí),委托可以調(diào)用多個(gè)方法。 這被稱為多播。 若要向委托的方法列表(調(diào)用列表)添加其他方法,只需使用加法運(yùn)算符或加法賦值運(yùn)算符(“+”或“+=”)添加兩個(gè)委托。 例如:
var obj = new MethodClass();
Callback d1 = obj.Method1;
Callback d2 = obj.Method2;
Callback d3 = DelegateMethod;
//Both types of assignment are valid.
Callback allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;
若要?jiǎng)h除調(diào)用列表中的方法,請使用減法運(yùn)算符或減法賦值運(yùn)算符(- 或 -=)。 例如:
//remove Method1
allMethodsDelegate -= d1;
// copy AllMethodsDelegate while removing d2
Callback oneMethodDelegate = allMethodsDelegate - d2;
1.3.1 多播委托的執(zhí)行順序
委托的調(diào)用列表是一個(gè)有序的委托集合,其列表中的每個(gè)元素都會(huì)調(diào)用委托所代表的一個(gè)方法。調(diào)用列表可以包含重復(fù)的方法。在調(diào)用過程中,方法按照它們在調(diào)用列表中出現(xiàn)的順序進(jìn)行調(diào)用。委托嘗試調(diào)用其調(diào)用列表中的每個(gè)方法;重復(fù)方法每次出現(xiàn)在調(diào)用列表中都會(huì)被調(diào)用一次。委托是不可變的;一旦創(chuàng)建,委托的調(diào)用列表就不會(huì)更改。
- 委托是immutable的解釋:委托是不可變的,你永遠(yuǎn)不會(huì)改變委托。任何看似改變委托的方法實(shí)際上都是在創(chuàng)建一個(gè)新實(shí)例。(Are System.MulticastDelegate’s thread-safe?)
1.3.2 多播執(zhí)行中斷與返回值
如果調(diào)用的方法引發(fā)異常,該方法將停止執(zhí)行,該異常將傳遞回委托的調(diào)用方,并且不會(huì)調(diào)用調(diào)用列表中的其余方法。 捕獲調(diào)用方中的異常不會(huì)改變此行為。
當(dāng)委托調(diào)用的方法的簽名包含返回值時(shí),委托將返回調(diào)用列表中最后一個(gè)元素的返回值。 當(dāng)簽名包含通過引用傳遞的參數(shù)時(shí),參數(shù)的最終值是調(diào)用列表中按順序執(zhí)行并更新參數(shù)值的每個(gè)方法的結(jié)果。
1.4 Delegate類、MulticastDelegate類
類 Delegate 是委托類型的基類。 但是,只有系統(tǒng)和編譯器才能從 Delegate 類或 MulticastDelegate 類顯式派生。 此外,不允許從委托類型派生新類型。 類 Delegate 不被視為委托類型;它是用于派生委托類型的類。
Delegate有幾個(gè)重要的屬性和方法:
-
public object
Target
屬性:獲取當(dāng)前委托調(diào)用實(shí)例方法時(shí)的類實(shí)例。如果委托表示實(shí)例方法,則為當(dāng)前委托調(diào)用實(shí)例方法的對象;如果委托表示靜態(tài)方法,則為null。 -
public MethodInfo
Method
屬性:獲取由委托表示的方法。 -
Combine(Delegate, Delegate)
方法:連接兩個(gè)委托的調(diào)用列表。public static Delegate? Combine (Delegate? a, Delegate? b);
返回一個(gè)新的委托,其調(diào)用列表按照a和b的順序連接在一起。如果b為null,則返回a;如果a為null引用,則返回b;如果a和b都為null引用,則返回null引用。
-
Delegate.Remove(Delegate, Delegate)
方法:從前一個(gè)委托的調(diào)用列表移除后一個(gè)委托的調(diào)用列表。public static Delegate? Remove (Delegate? source, Delegate? value);
如果在 source 的調(diào)用列表中找到 value 的調(diào)用列表,則將 source 的調(diào)用列表移除 value 的調(diào)用列表,并形成一個(gè)新的帶有調(diào)用列表的委托;如果source調(diào)用列表匹配多個(gè)value中的調(diào)用列表,則移除最后一個(gè)匹配項(xiàng)。如果 value 為空或在 source 的調(diào)用列表中找不到 value 的調(diào)用列表,則返回 source。如果 value 的調(diào)用列表等于 source 的調(diào)用列表,或 source 為空引用,則返回空引用null。
用代碼輔助理解“最后一個(gè)匹配項(xiàng)/出現(xiàn)項(xiàng)”(the last occurrence):
public class Ringleader { public delegate void StateChangeHandler(); private static void Method1() { Console.Write("1 "); } private static void Method2() { Console.Write("2 "); } private static void Method3() { Console.Write("3 "); } private static void Method4() { Console.Write("4 "); } public static void Main(string[] args) { StateChangeHandler stateChangeHandler1 = Method1; StateChangeHandler stateChangeHandler2 = Method2; StateChangeHandler stateChangeHandler3 = Method3; StateChangeHandler stateChangeHandler4 = Method4; var stateChangeHandler12 = stateChangeHandler1 + stateChangeHandler2; var stateChangeHandler34 = stateChangeHandler3 + stateChangeHandler4; var stateChangeHandler24 = stateChangeHandler2 + stateChangeHandler4; var stateChangeHandler13 = stateChangeHandler1 + stateChangeHandler3; var stateChangeHandler32 = stateChangeHandler3 + stateChangeHandler2; var stateChangeHandler1234 = stateChangeHandler12 + stateChangeHandler34; stateChangeHandler1234();Console.WriteLine("");//1 2 3 4 var stateChangeHandler1324 = stateChangeHandler13 + stateChangeHandler24; stateChangeHandler1324();Console.WriteLine("");//1 3 2 4 stateChangeHandler1324 -= stateChangeHandler12; stateChangeHandler1324();Console.WriteLine("");//1 3 2 4 (1324-12=1324) stateChangeHandler1324 -= stateChangeHandler32; stateChangeHandler1324();Console.WriteLine("");//1 4 (1324-32=14) var stateChangeHandler13244321312 = stateChangeHandler1 + stateChangeHandler3 + stateChangeHandler2 + stateChangeHandler4 + stateChangeHandler4 + stateChangeHandler3 + stateChangeHandler2 + stateChangeHandler1 + stateChangeHandler3 + stateChangeHandler1 + stateChangeHandler2; stateChangeHandler13244321312();Console.WriteLine("");//1 3 2 4 4 3 2 1 3 1 2 stateChangeHandler13244321312 -= stateChangeHandler3 + stateChangeHandler2; stateChangeHandler13244321312();Console.WriteLine("");//1 3 2 4 4 1 3 1 2 (13244321312-(3+2)= 132441312) } }
因?yàn)槲械?/-/-=/+=運(yùn)算本質(zhì)就是調(diào)用Combine或Remove方法,所以上面用運(yùn)算符驗(yàn)證結(jié)果是相同的。
MulticastDelegate繼承自Delegate,且包含invocationList參數(shù),代表委托調(diào)用列表,可以通過Delegate[] GetInvocationList()
方法獲取這個(gè)列表。
1.4.1 運(yùn)行時(shí)添加的四個(gè)成員方法
如果查看Delegate和MulticastDelegate類源碼的話,你可能會(huì)奇怪,前面提到的Invoke、BeginInvoke、EndInvoke為什么會(huì)沒有,其實(shí)這是運(yùn)行時(shí)交由底層聲明并管理的:
一個(gè)委托聲明會(huì)被轉(zhuǎn)換為一個(gè)繼承自 MulticastDelegate(而不是 CLI 規(guī)范中指定的 Delegate)的類。該類始終具有確切的 4 個(gè)成員,它們的運(yùn)行時(shí)實(shí)現(xiàn)由 CLR 提供:
-
一個(gè)構(gòu)造函數(shù)
.ctor(object,native int)
,接受一個(gè)對象object和一個(gè) IntPtr。對象是 Delegate.Target,IntPtr 是目標(biāo)方法的地址,也即Delegate.Method。這些成員在調(diào)用委托時(shí)稍后使用,Target 屬性在委托綁定的方法是實(shí)例方法時(shí)提供 this 引用,對于靜態(tài)方法則為 null。Method 屬性決定要調(diào)用的方法。 -
一個(gè)
Invoke()
方法。該方法的參數(shù)是動(dòng)態(tài)生成的,并與委托聲明匹配。調(diào)用 Invoke() 方法在同一線程上運(yùn)行委托目標(biāo)方法,即同步調(diào)用。通常只需使用括號語法糖即可,通過對象名稱后跟括號來調(diào)用委托對象。 -
一個(gè)
BeginInvoke()
方法,提供了一種進(jìn)行異步調(diào)用的方式。該方法在目標(biāo)方法正在忙于執(zhí)行時(shí)迅速完成,類似于 ThreadPool.QueueUserWorkItem,但具有類型安全的參數(shù)。返回類型始終為 System.IAsyncResult,用于查找異步調(diào)用何時(shí)完成,并提供給 EndInvoke() 方法。第一個(gè)參數(shù)是一個(gè)可選的 System.AsyncCallback 委托對象,當(dāng)異步調(diào)用完成時(shí),其目標(biāo)將自動(dòng)被調(diào)用。第二個(gè)參數(shù)是一個(gè)可選的對象,它將原樣傳遞給回調(diào),對于跟蹤狀態(tài)很有用。附加參數(shù)是動(dòng)態(tài)生成的,并與委托聲明匹配。 -
一個(gè)
EndInvoke()
方法。它接受一個(gè) IAsyncResult 類型的單一參數(shù),您必須傳遞從 BeginInvoke() 得到的參數(shù)。它完成異步調(diào)用并釋放資源。
參見:
- Where are CLR-defined methods like [delegate].BeginInvoke documented?
- ECMA-335, 6th edition, June 2012
1.4.2 ildasm.exe查看文件
可以通過反匯編代碼來查看編譯后的委托被運(yùn)行時(shí)底層添加了什么。
Rider創(chuàng)建新solution時(shí)使用Console Application,運(yùn)行項(xiàng)目時(shí),根目錄會(huì)多出一個(gè)bin>debug>*.exe一個(gè)執(zhí)行文件,將這個(gè)文件拖入系統(tǒng)自帶的C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7.1 Tools\ildasm.exe 反匯編工具中。
如下示代碼,聲明了一個(gè)委托,實(shí)際上是創(chuàng)建了一個(gè)繼承自MulticastDelegate的類,這個(gè)類包含一個(gè)構(gòu)造器,和Invoke/BeginInvoke/EndInvoke三個(gè)方法。
namespace AsyncDelegate
{
public class DecompiledTest2
{
public delegate void StateChangeHandler();
}
}
上面提到的“委托的+/-/-=/+=運(yùn)算本質(zhì)就是調(diào)用Combine或Remove方法”也可以用反匯編驗(yàn)證這個(gè)說法。
參考:
- C#數(shù)據(jù)結(jié)構(gòu)-委托(Delegate)和事件(Event)
- c#中委托和事件? - 小約翰的回答 - 知乎
- 初識(shí)Ildasm.exe–IL反編譯的實(shí)用工具
- ildasm.exe(IL反匯編程序)
1.5 常用的委托類型Action、Func、Predicate
.NET Core 框架包含幾個(gè)在需要委托類型時(shí)可重用的類型。 這些是泛型定義,因此需要新的方法聲明時(shí)可以聲明自定義。
-
第一個(gè)類型是 Action 類型和一些變體:
public delegate void Action(); public delegate void Action<in T>(T arg); public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2); // Other variations removed for brevity.
-
幾種可用于返回值的委托類型的泛型委托類型:
public delegate TResult Func<out TResult>(); public delegate TResult Func<in T1, out TResult>(T1 arg); public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2); // Other variations removed for brevity
-
一種專門的委托類型 Predicate,此類型返回單個(gè)值的測試結(jié)果:
public delegate bool Predicate<in T>(T obj);
更多內(nèi)容參見:強(qiáng)類型委托
二 事件
事件是對象用于(向系統(tǒng)中的所有相關(guān)組件)廣播已發(fā)生事情的一種方式。 任何其他組件都可以訂閱事件,并在事件引發(fā)時(shí)得到通知。比如這些事件會(huì)報(bào)告鼠標(biāo)移動(dòng)、按鈕點(diǎn)擊和類似的交互。
事件的語言設(shè)計(jì)針對這些目標(biāo):
- 在事件源和事件接收器之間啟用非常小的耦合。 這兩個(gè)組件可能不會(huì)由同一個(gè)組織編寫,甚至可能會(huì)通過完全不同的計(jì)劃進(jìn)行更新。
- 訂閱事件并從同一事件取消訂閱應(yīng)該非常簡單。
- 事件源應(yīng)支持多個(gè)事件訂閱服務(wù)器。 它還應(yīng)支持不附加任何事件訂閱服務(wù)器。
你會(huì)發(fā)現(xiàn)事件的目標(biāo)與委托的目標(biāo)非常相似。 因此,事件語言支持基于委托語言支持構(gòu)建。
用于定義事件以及訂閱或取消訂閱事件的語法是對委托語法的擴(kuò)展。
如下定義了StateChangeHandler委托類型的事件,事件用event
關(guān)鍵字修飾:
public delegate void StateChangeHandler();
public event StateChangeHandler StatechangeEvent;
事件的調(diào)用、注冊和注銷方法和委托相同:
StatechangeEvent += ()=>{ Console.WriteLine("事件觸發(fā)~");};
StatechangeEvent?.Invoke();
2.1 事件的使用
下面是完整實(shí)例:
using System;
namespace AsyncDelegate
{
public delegate void StateChangeHandler();
public class EventTest
{
public event StateChangeHandler StatechangeEvent;
public static void Method()
{
Console.WriteLine("事件觸發(fā)~");
}
public static void Main(string[] args)
{
var eventTest = new EventTest();
eventTest.StatechangeEvent += Method;
eventTest.StatechangeEvent?.Invoke();
}
}
}
為了弄清e(cuò)vent字段的作用,利用Ildasm.exe反匯編代碼。
發(fā)現(xiàn)系統(tǒng)自動(dòng)添加了一個(gè)StateChangeHandler委托類型的私有變量StatechangeEvent,同時(shí)添加了兩個(gè)方法add_StatechangeEvent、remove_StatechangeEvent.
這等同于下面的寫法:
namespace AsyncDelegate
{
public delegate void StateChangeHandler();
public class EventTest
{
public event StateChangeHandler StatechangeEvent
{
add => _stateChangeHandler += value;
remove => _stateChangeHandler -= value;
}
private StateChangeHandler _stateChangeHandler;
public static void Method()
{
Console.WriteLine("事件觸發(fā)~");
}
public static void Main(string[] args)
{
var eventTest = new EventTest();
eventTest.StatechangeEvent += Method;
eventTest._stateChangeHandler?.Invoke();
}
}
}
多了一個(gè)私有StateChangeHandler變量,以及一個(gè)事件訪問器,其中add、remove方法里的value代指使用“+/-/+=/-=”委托運(yùn)算符時(shí)對應(yīng)的方法名。
可以對比一下上面代碼反匯編后的結(jié)果:
當(dāng)在其他類使用上面事件并試圖調(diào)用其Invoke方法時(shí),編輯器報(bào)錯(cuò):The event 'StatechangeEvent' can only appear on the left hand side of += or -= (except when used from within the class 'AsyncDelegate.EventTest')
,表明事件只向外暴露add和remove訪問器,Invoke并沒有暴露,所以無法訪問。
2.2 事件訪問器
事件訪問器可以自定義,除了上面的例子,還可以如下所示:
public event StateChangeHandler StatechangeEvent
{
add
{
_stateChangeHandler += value;
Console.WriteLine("已添加{0}方法",value.GetMethodInfo().Name);
}
remove
{
_stateChangeHandler -= value;
Console.WriteLine("已移除{0}方法",value.GetMethodInfo().Name);
}
}
這樣可以打印所添加的方法。
還可以在多線程環(huán)境下對事件所在的對象進(jìn)行加鎖,比如:
public event StateChangeHandler StatechangeEvent
{
add
{
lock (this)
{
_stateChangeHandler += value;
}
}
remove
{
lock (this)
{
_stateChangeHandler -= value;
}
}
}
經(jīng)測試,只要有自定義事件訪問器,系統(tǒng)就不會(huì)自動(dòng)為你添加對應(yīng)私有委托變量。
2.3 事件的本質(zhì)、事件與委托的區(qū)別
當(dāng)你刪除上面代碼的event字段,你將會(huì)發(fā)現(xiàn),代碼依然可以運(yùn)行。
namespace AsyncDelegate
{
public delegate void StateChangeHandler();
public class EventTest
{
public StateChangeHandler StatechangeEvent;
}
class OtherClass
{
public static void Method()
{
Console.WriteLine("事件觸發(fā)~");
}
public static void Main(string[] args)
{
var eventTest = new EventTest();
eventTest.StatechangeEvent += Method;
eventTest.StatechangeEvent?.Invoke();
}
}
}
唯一區(qū)別就是現(xiàn)在事件可以在外部Invoke了。
通過之前的介紹和分析,不難明白,event字段本質(zhì)就是對委托進(jìn)行私有訪問限制,事件的本質(zhì)就是委托,只不過系統(tǒng)會(huì)對用event字段修飾的委托進(jìn)行了特殊處理,比如自動(dòng)生成一個(gè)私有的委托變量,添加兩個(gè)事件訪問器,同時(shí)禁止外部類對事件的Invoke等方法調(diào)用。
2.4 觀察者風(fēng)格的事件案例
前面的案例還是把事件定義、事件觸發(fā)、事件訂閱都放在一個(gè)類中?,F(xiàn)在對其進(jìn)行改寫,使其符合事件使用時(shí)的解耦場景:
using System;
namespace AsyncDelegate
{
public delegate void StateChangeHandler();
// 事件定義,在觀察者模式中稱作subject主題
public class EventTest
{
public event StateChangeHandler StatechangeEvent;
public void OnStateChange()
{
Console.WriteLine("事件觸發(fā)~");
StatechangeEvent?.Invoke();
}
}
// 觀察者定義,在觀察者模式中稱作Observer觀察者
class Observer
{
public void Method()
{
Console.WriteLine("觀察者接收到事件觸發(fā)");
}
}
// 主程序,注冊觀察者、觸發(fā)事件等
class Ringleader{
public static void Main(string[] args)
{
var eventTest = new EventTest();
var observer = new Observer();
eventTest.StatechangeEvent += observer.Method;
eventTest.OnStateChange();
}
}
}
2.5 標(biāo)準(zhǔn) .NET 模式的事件
如果要遵循標(biāo)準(zhǔn) .NET 模式的事件,可以利用.NET 類庫中的EventHandler 委托(當(dāng)然你也可以自定義遵循這種風(fēng)格的委托),其定義如下:
public delegate void EventHandler(object sender, EventArgs e);
其中
- object sender:事件源
- EventArgs e:不包含事件數(shù)據(jù)的對象。
將上面的例子改寫成標(biāo)準(zhǔn) .NET 模式的事件:
using System;
namespace AsyncDelegate
{
public class EventHandlerTest
{
public event EventHandler StatechangeEvent;
public void OnStateChange()
{
StatechangeEvent?.Invoke(this,new MyEventArgs("事件觸發(fā)啦~"));
// StatechangeEvent?.Invoke(this,EventArgs.Empty);//不帶參數(shù)的寫法
}
}
class MyEventArgs : EventArgs
{
public string Msg { get; }
public MyEventArgs(){}
public MyEventArgs(string msg)
{
this.Msg = msg;
}
}
class Observer
{
public void Method(Object sender, EventArgs e)
{
Console.WriteLine($"觀察者接收到事件發(fā)出的消息:{((MyEventArgs)e).Msg}");
Console.WriteLine($"事件來源:{sender.GetType()}");
}
}
class Ringleader{
public static void Main(string[] args)
{
var eventTest = new EventHandlerTest();
var observer = new Observer();
eventTest.StatechangeEvent += observer.Method;
eventTest.OnStateChange();
}
}
// 打?。?/span>
// 觀察者接收到事件發(fā)出的消息:事件觸發(fā)啦~
// 事件來源:AsyncDelegate.EventHandlerTest
}
三 觀察者模式
在講事件的時(shí)候,反復(fù)提到觀察者模式。但奇怪的是,很多人包括官方文檔都使用 “ 訂閱、發(fā)布 ” 這種發(fā)布訂閱模式常用的詞匯。那委托和事件到底是觀察者模式還是發(fā)布訂閱模式呢?
我個(gè)人理解,在24種基本設(shè)計(jì)模式是沒有發(fā)布訂閱模式的,發(fā)布訂閱模式是觀察者模式的一種變體,通常會(huì)多一層Topic/event管理中心,訂閱和發(fā)布者解耦程度會(huì)比觀察者模式種主題和觀察者更大,所以我還是采用 “ C#委托和事件是觀察者模式 ” 這一說法。
參考:
- 觀察者模式與訂閱發(fā)布模式的區(qū)別
- 面試官:說說你對發(fā)布訂閱、觀察者模式的理解?區(qū)別?
我在學(xué)習(xí)委托和事件的時(shí)候一直有個(gè)疑問,即C#為什么要費(fèi)這個(gè)勁搞出委托這一套東西?它比用接口形式實(shí)現(xiàn)觀察者模式有什么優(yōu)勢?
3.1 委托 vs IObserable
以下是我個(gè)人的一些思考,不一定對,請選擇性參考。
C#的委托和事件本質(zhì)是一種消息通知模式,即一種以觀察者模式進(jìn)行消息通知的形式。
比如程序中會(huì)有各種突發(fā)事件,像鼠標(biāo)點(diǎn)擊、鼠標(biāo)移動(dòng)、鍵盤敲擊等,每個(gè)事件都會(huì)引發(fā)某些行為,這個(gè)可以由用戶自定義,比如在游戲中,點(diǎn)擊鼠標(biāo)左鍵將引發(fā)開槍的行為,那么對于“點(diǎn)擊鼠標(biāo)左鍵”這個(gè)事件的觸發(fā)(trigger)如何引發(fā)(raise)“開槍”這個(gè)行為呢,有三種,我們將這些事件和行為統(tǒng)一稱作event和action,那么就有:
- 直接觸發(fā),event觸發(fā)–直接引發(fā)->action
- 輪詢,action端輪詢(event是否觸發(fā)),當(dāng)event觸發(fā),則執(zhí)行action
- 觀察者模式,將event的定義和觸發(fā)端叫做主題(Subject),將action的執(zhí)行端稱作觀察者(Observer),Subject定義了一個(gè)針對某個(gè)event的容器,讓對這個(gè)event感興趣的的觀察者將自身注冊進(jìn)這個(gè)容器,當(dāng)Subject觸發(fā)event時(shí),就會(huì)依次調(diào)用這個(gè)容器中所有觀察者對應(yīng)的方法(當(dāng)然也有異步調(diào)用)。
C#中處理方式和3類似,只是進(jìn)一步將這個(gè)容器抽象出來,委托給Delegate處理(也許這就是為啥它叫做 “ 委托 ”),讓Delegate處理事件定義和觀察者注冊,并且觀察者的注冊不再是自身實(shí)例,只需要是處理方法本身即可,可以是靜態(tài)方法、實(shí)例方法、匿名方法或者是λ表達(dá)式,提高了使用靈活性。
盡管C#擁有委托這個(gè)機(jī)制,它依然加入了觀察者模式的接口實(shí)現(xiàn)模式,即IObservable / IObserver
,有人認(rèn)為是委托的調(diào)用列表這個(gè)集合有不足之處,不如Hashset這種優(yōu)化了的容器:
同時(shí)上面那個(gè)作者認(rèn)為委托機(jī)制會(huì)產(chǎn)生一些垃圾
雖然盡管這些垃圾生命周期很短
從性能方面來說,另一位網(wǎng)友認(rèn)為接口形式的效率會(huì)比委托快,但如果性能和垃圾不成問題時(shí),他更傾向使用簡潔靈活且優(yōu)雅的委托。
來源:Delegates vs Observer Pattern
但直到2010.4月才首次在.NET Framework 4.0 引入 IObservable/IObserver,可能是為以后跨平臺(tái)跨語言作準(zhǔn)備的,不是很懂。
來源:Implement Observer Pattern in .NET (3 Techniques)
來源:what are the differences between .net observer pattern variations (IObservable and event delegation)?
這里就不再糾結(jié)IObservable提出的歷史了。對委托以及.net與Java的糾葛感興趣的可以參考下面鏈接:
- CLR 相比 JVM有哪些先進(jìn)之處? - RednaxelaFX的回答 - 知乎
- 微軟當(dāng)年的 J++ 究竟是什么?為什么 Sun 要告它? - RednaxelaFX的回答 - 知乎
3.2 觀察者模式模型
此節(jié)內(nèi)容主要翻譯自微軟的技術(shù)文檔,限于篇幅,我這里就不摘錄了,感興趣的可以訪問我翻譯的這篇文章
Exploring the Observer Design Pattern微軟技術(shù)文章翻譯
這篇文章詳述了觀察者模式的模型,并用四種方式(Hashset、IObesever、Delegate、Event patern)進(jìn)行實(shí)現(xiàn)。
文章來源:http://www.zghlxwxcb.cn/news/detail-759334.html
四 總結(jié)
本文針對C#的委托和事件,詳述了委托的使用、異步委托、多播委托、事件的使用、事件訪問器等基本知識(shí),并利用ildasm工具查看編譯后代碼,探索委托和事件的本質(zhì)和區(qū)別。同時(shí)研究了委托和事件背后的觀察者模式,辨析了接口形式的實(shí)現(xiàn)方式和委托的區(qū)別。文章來源地址http://www.zghlxwxcb.cn/news/detail-759334.html
到了這里,關(guān)于【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!