国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件)

這篇具有很好參考價(jià)值的文章主要介紹了【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件)。希望對大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

轉(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的簽名中的方式。

  1. 下面的代碼示例顯示了 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);
}
  1. 異步執(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?)
    【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)
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 提供:

  1. 一個(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)用的方法。

  2. 一個(gè) Invoke() 方法。該方法的參數(shù)是動(dòng)態(tài)生成的,并與委托聲明匹配。調(diào)用 Invoke() 方法在同一線程上運(yùn)行委托目標(biāo)方法,即同步調(diào)用。通常只需使用括號語法糖即可,通過對象名稱后跟括號來調(diào)用委托對象。

  3. 一個(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)生成的,并與委托聲明匹配。

  4. 一個(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();
    }
}

【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)

上面提到的“委托的+/-/-=/+=運(yùn)算本質(zhì)就是調(diào)用Combine或Remove方法”也可以用反匯編驗(yàn)證這個(gè)說法。

【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)

參考:

  • 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í)可以聲明自定義。

  1. 第一個(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.
    
  2. 幾種可用于返回值的委托類型的泛型委托類型:

    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
    
  3. 一種專門的委托類型 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反匯編代碼。

【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)

默認(rèn)事件訪問器的反匯編結(jié)果
??

發(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é)果:
【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)

自定義事件訪問器的反匯編結(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并沒有暴露,所以無法訪問。
【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)

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)私有委托變量。

【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)

當(dāng)存在自定義事件訪問器時(shí),不會(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();
        }
    }
}

【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)

去除event字段的反匯編結(jié)果
??

唯一區(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#委托和事件是觀察者模式 ” 這一說法。
【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)

參考:

  • 觀察者模式與訂閱發(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,那么就有:

  1. 直接觸發(fā),event觸發(fā)–直接引發(fā)->action
  2. 輪詢,action端輪詢(event是否觸發(fā)),當(dāng)event觸發(fā),則執(zhí)行action
  3. 觀察者模式,將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)化了的容器:

【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)

同時(shí)上面那個(gè)作者認(rèn)為委托機(jī)制會(huì)產(chǎn)生一些垃圾

【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)

雖然盡管這些垃圾生命周期很短

【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)

從性能方面來說,另一位網(wǎng)友認(rèn)為接口形式的效率會(huì)比委托快,但如果性能和垃圾不成問題時(shí),他更傾向使用簡潔靈活且優(yōu)雅的委托。

【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)

來源:Delegates vs Observer Pattern

但直到2010.4月才首次在.NET Framework 4.0 引入 IObservable/IObserver,可能是為以后跨平臺(tái)跨語言作準(zhǔn)備的,不是很懂。

【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)

來源:Implement Observer Pattern in .NET (3 Techniques)

【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)

來源: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)。

【C#學(xué)習(xí)筆記】委托與事件 (從觀察者模式看C#的委托與事件),c#,c#,筆記,Delegate,event,觀察者模式,IObserver,事件的本質(zhì)

觀察者模式模型
??

四 總結(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)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Unity 事件監(jiān)聽與廣播(高度解耦合,觀察者模式)

    Unity 事件監(jiān)聽與廣播(高度解耦合,觀察者模式)

    使用觀察者模式降低模塊間的耦合性 通過C# 的 Dictionary 存放事件碼和事件的委托 添加事件: 判斷字典是否有該事件碼,沒有添加 判斷當(dāng)前委托類型與添加的事件碼的類型是否一致 最后訂閱該事件 移除事件: 先判斷事件碼是否存在 取消訂閱 最后判斷事件碼是否為空,是

    2024年02月12日
    瀏覽(16)
  • c#設(shè)計(jì)模式-行為型模式 之 觀察者模式

    c#設(shè)計(jì)模式-行為型模式 之 觀察者模式

    又被稱為發(fā)布-訂閱(Publish/Subscribe)模式,它定義了一種一對多的依賴關(guān)系,讓多個(gè)觀察者 對象同時(shí)監(jiān)聽某一個(gè)主題對象。這個(gè)主題對象在狀態(tài)變化時(shí),會(huì)通知所有的觀察者對象,使他們能夠自 動(dòng)更新自己。 在觀察者模式中有如下角色: Subject:抽象主題(抽象被觀察者)

    2024年02月14日
    瀏覽(23)
  • [Unity] No.3 EventManager事件管理 與 觀察者模式

    前文講到了InputManager,在其后需要一個(gè)事件的管理者用于調(diào)度任務(wù)的執(zhí)行,包括: ①查看發(fā)生了什么事; ②出事后看看都需要通知誰干什么事; 以上兩個(gè)內(nèi)容就對應(yīng)了EventManager中關(guān)鍵的 監(jiān)聽 和 回調(diào) ,而在講EventManager之前還需要知道,它是符合觀察者這一模式的。 此處不

    2024年02月08日
    瀏覽(21)
  • 【設(shè)計(jì)模式——學(xué)習(xí)筆記】23種設(shè)計(jì)模式——觀察者模式Observer(原理講解+應(yīng)用場景介紹+案例介紹+Java代碼實(shí)現(xiàn))

    【設(shè)計(jì)模式——學(xué)習(xí)筆記】23種設(shè)計(jì)模式——觀察者模式Observer(原理講解+應(yīng)用場景介紹+案例介紹+Java代碼實(shí)現(xiàn))

    有一個(gè)天氣預(yù)報(bào)項(xiàng)目,需求如下: 氣象站可以將每天測量到的溫度、濕度、氣壓等等以公告的形式發(fā)布出去(比如發(fā)布到自己的網(wǎng)站或第三方) 需要設(shè)計(jì)開放型API,便于其他第三方也能接入氣象站獲取數(shù)據(jù) 提供溫度、氣壓、濕度的接口 測量數(shù)據(jù)更新時(shí),要能實(shí)時(shí)的通知給第三

    2024年02月14日
    瀏覽(20)
  • 從源碼Debug深入spring事件機(jī)制,基于觀察者模式仿寫spring事件監(jiān)聽骨架

    從源碼Debug深入spring事件機(jī)制,基于觀察者模式仿寫spring事件監(jiān)聽骨架

    定義一個(gè)事件 定義兩個(gè)listener 注入spring容器里的ApplicationEventPublisher對象,發(fā)布事件 從 eventPublisher.publishEvent(new MyEvent(\\\"xxx\\\")); 進(jìn)去很容易就能找到,可以發(fā)現(xiàn) SimpleApplicationEventMulticaster這個(gè)事件發(fā)布對象持有所有l(wèi)istenter對象及MyEvent對象 , 事件發(fā)布過程其實(shí)就是遍歷拿到每個(gè)li

    2024年02月12日
    瀏覽(16)
  • 《設(shè)計(jì)模式的藝術(shù)》筆記 - 觀察者模式

    ? ? ? ? 觀察者模式定義對象之間的一種一對多依賴關(guān)系,使得每當(dāng)一個(gè)對象狀態(tài)發(fā)生改變時(shí),其相關(guān)依賴對象皆得到通知并被自動(dòng)更新。 myclass.h myclass.cpp main.cpp ? ? ? ? 1.?觀察者模式可以實(shí)現(xiàn)表示層和數(shù)據(jù)邏輯層的分離。它定義了穩(wěn)定的消息更新傳遞機(jī)制,并抽象了更新

    2024年01月25日
    瀏覽(20)
  • 學(xué)習(xí)設(shè)計(jì)模式之觀察者模式,但是寶可夢

    學(xué)習(xí)設(shè)計(jì)模式之觀察者模式,但是寶可夢

    作者在準(zhǔn)備秋招中,學(xué)習(xí)設(shè)計(jì)模式,做點(diǎn)小筆記,用寶可夢為場景舉例,有錯(cuò)誤歡迎指出。 觀察者模式定義了一種一對多的依賴關(guān)系,一個(gè)對象的狀態(tài)改變,其他所有依賴者都會(huì)接收相應(yīng)的通知。 所以, 何時(shí)使用: 一個(gè)對象的狀態(tài)改變,其他所有依賴對象都要知道 意圖: 定

    2024年02月11日
    瀏覽(23)
  • 觀察者模式(上):詳解各種應(yīng)用場景下觀察者模式的不同實(shí)現(xiàn)方式

    ????????從今天起,我們開始學(xué)習(xí)行為型設(shè)計(jì)模式。我們知道,創(chuàng)建型設(shè)計(jì)模式主要解決“對象的創(chuàng)建”問題,結(jié)構(gòu)型設(shè)計(jì)模式主要解決“類或?qū)ο蟮慕M合或組裝”問題,那行為型設(shè)計(jì)模式主要解決的就是“ 類或?qū)ο笾g的交互 ”問題。 原理及應(yīng)用場景剖析 在對象之間

    2024年02月16日
    瀏覽(85)
  • 【C++ 觀察者模式 思想理解】C++中的觀察者模式:松耦合設(shè)計(jì)與動(dòng)態(tài)交互的藝術(shù),合理使用智能指針觀察者

    【C++ 觀察者模式 思想理解】C++中的觀察者模式:松耦合設(shè)計(jì)與動(dòng)態(tài)交互的藝術(shù),合理使用智能指針觀察者

    在進(jìn)入技術(shù)細(xì)節(jié)之前,理解觀察者模式(Observer Pattern)的基本概念和它在現(xiàn)代編程中的重要性是至關(guān)重要的。 觀察者模式是一種設(shè)計(jì)模式,它定義了對象間的一種一對多的依賴關(guān)系,當(dāng)一個(gè)對象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對象都得到通知并自動(dòng)更新。在C++中,這個(gè)

    2024年01月24日
    瀏覽(48)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包