徹底搞清楚c#中的委託和事件

micDavid發表於2019-07-17

一、什麼是委託呢?

聽著名字挺抽象,確實不好理解。面試官最喜歡考察這個,而且更喜歡問:“委託和事件有何異同?”。如果對一些知識點沒有想明白,那麼很容易被繞進去。研究任何事物,我們不妨從它的定義開始,委託也不例外。那麼先來看c#中的委託定義,先來個例子:

public delegate void GetPacage(string code);

這個委託,看起來就是個方法簽名,取包裹,需要驗證碼。與方法簽名不同的地方,在於多了一個delegate。c#中不乏一些便利好用的語法,比如foreach、yield,每一個關鍵字背後都有一段故事。delegate的背後,又有什麼故事呢?其實就是c#編譯器幫我們做了些什麼事情。要知道這個,我們得看生成的IL,如何檢視IL?請看下圖:

vs命令列中輸入 ildasm,會開啟一個反編譯的視窗,選擇我們的程式集,如下圖:

從圖中可以看出:

1、委託的本質就是一個密封類,這個類繼承了MulticastDelegate(多播委託)

2、委託的建構函式,有兩個引數,一個型別是IntPtr,用來接收方法的,如下圖:

3、可以同步呼叫(Invoke),也可以非同步呼叫 (BeginInvoke、EndInvoke)

注:

1、多播委託:一個委託可以代表多個相同簽名的方法,當委託被呼叫時,這些方法會依次執行

2、IntPtr表示視窗的時候,叫它“控制程式碼”,表示方法時,叫它“指標”

3、非同步呼叫:會產生一個執行緒,非同步執行

二、委託有什麼用?

在js中,並沒有提委託的概念,卻有“回撥”,比如ajax回撥。把一個函式傳遞到另外一個函式裡執行,是非常自然的事情。但是在c#中,不能直接把方法名傳遞進去。所以創造了委託這麼個型別。c#中的委託也是為了回撥。委託有什麼好處?舉個例子:皇帝頒發聖旨,得派一個大臣去。大臣到了目的地,宣讀聖旨後,這才得以執行。這說明以下兩點:

1、委託有很好的封裝性

2、委託的例項化與它的執行是在不同的物件中完成的

三、委託與代理

我說的代理,是指設計模式中的代理。代理與實際物件有相同的介面,委託與實際方法有相同的方法簽名。這就是它們類似的地方。無論是相同的介面,還是相同的方法簽名,其本質是遵循相同的協議。這是它們僅存的相似點。不同點多了,如目的不同,委託只是回撥,而代理是對實際物件的訪問控制。

四、委託和事件

先看一段程式碼:

 1     public delegate void GetPacage(string code);
 2     public class Heater
 3     {
 4         public event EventHandler OnBoiled;
 5 
 6         public event GetPacage PackageHandler;
 7 
 8         private void RasieBoiledEvent()
 9         {
10 
11             if (OnBoiled == null)
12             {
13                 Console.WriteLine("加熱完成處理訂閱事件為空");
14             }
15             else
16             {
17                 OnBoiled(this, new EventArgs());
18             }
19         }
20         public void Begin()
21         {
22             heatTime = 5;
23             Heat();
24             Console.WriteLine("加熱器已經開啟", heatTime);
25 
26         }
27         private int heatTime;
28         private void Heat()
29         {
30             Console.WriteLine("當前Heat Method執行緒:" + Thread.CurrentThread.ManagedThreadId);
31             while (true)
32             {
33                 Console.WriteLine("加熱還需{0}秒", heatTime);
34 
35                 if (heatTime == 0)
36                 {
37                     RasieBoiledEvent();
38                     return;
39                 }
40                 heatTime--;
41                 Thread.Sleep(1000);
42 
43             }
44         }
45     }

這個是加熱器例子,為了研究事件,裡面混合了自定義的委託和事件。我們看看第6行編譯後的程式碼(紅框):

編譯器幫我們做了如下的事情:

1、生成了一個私有的委託欄位

[CompilerGenerated, DebuggerBrowsable(DebuggerBrowsableState.Never)]
private GetPacage PackageHandler;

2、生成了新增和移除委託的方法

[CompilerGenerated]
public void add_PackageHandler(GetPacage value)
{
    GetPacage pacage2;
    GetPacage packageHandler = this.PackageHandler;
    do
    {
        pacage2 = packageHandler;
        GetPacage pacage3 = (GetPacage) Delegate.Combine(pacage2, value);
        packageHandler = Interlocked.CompareExchange<GetPacage>(ref this.PackageHandler, pacage3, pacage2);
    }
    while (packageHandler != pacage2);
}

這就是事件和委託的關係。有點像欄位和屬性的關係。那有人說,事件是一種包裝的委託,或者特殊的委託,那麼到底對不對呢?我覺得不對。比如我坐了公交車回家了,能說我是一個特殊的公交車嗎?不能說A事物擁有了B事物的能力,就說A是特殊的B。那到底該怎麼描述事件和委託之間的關係呢?事件基於委託,但並非委託。可以把事件看成委託的代理。在使用者看來,只有事件,而沒有委託。事件是對委託的包裝,這個沒錯,到底包裝了哪些東西?

1、保護委託欄位,對外不開放,所以外部物件沒法直接操作委託。提供了Add和Remove方法,供外部物件訂閱事件和取消事件

2、事件的處理方法在物件外部定義,而事件的執行是在物件的內部,至於事件的觸發,何時何地無所謂。

五、c#滑鼠鍵盤事件

此類事件的底層實現,一方面是訊息迴圈,另一方面是硬體中斷,或者兩者結合實現,有空了再研究。

六、經典面試題,貓叫、老鼠跑了,主人醒來了

 

   public delegate void ScreamHandler();

    public class Cat
    {
        public event ScreamHandler OnScream;

        public void Scream()
        {
            Console.WriteLine("貓叫了一聲");
            OnScream?.Invoke();
        }

    }
    public class Mouse
    {
        public Mouse(Cat c)
        {
            c.OnScream += () =>
            {
                Console.WriteLine("老鼠跑了");
            };
        }
    }

    public class People
    {
        public People(Cat c)
        {
            c.OnScream += () =>
            {
                Console.WriteLine("主人醒來了");
            };
        }
    }

 

客戶端呼叫:

  Cat cat = new Cat();
  Mouse m = new Mouse(cat);
  People p = new People(cat);
  cat.Scream();

執行結果:

 

相關文章