1. 程式人生 > >委託與事件(3)

委託與事件(3)

原創連結



網上講C#委託和事件的博文已經非常多了,其中也不乏一些深入淺出、條理清晰的文章。我之所以還是繼續寫,主要是藉機整理學習筆記、歸納總結從而理解更透徹,當然能夠以自己的理解和思路給其他人講明白更好。
另外,太長的文章會讓很多讀者失去興趣,所以我決定把這篇分成四個部分來介紹。分別是委託的基礎、委託的進階、事件的基礎和事件的進階。對使用委託與事件要求不高的同學可以跳過進階部分。

5. 什麼是事件?怎麼寫一個事件?

An event in C# is a way for a class to provide notifications to clients of that class when some interesting thing happens to an object.

所以這裡的事件,是指當某件有意思的事情發生時一個類給它的客戶端提供通知的方法。比如說電熱壺的溫度被警報器訂閱了,當溫度達到某一高度時就會觸發事件通知警報器發出報警聲音。

對,關於什麼是事件就是這麼簡單。那麼,怎麼寫一個事件呢?
定義一個事件需要兩步:
(1) 定義一個委託型別,它包含在事件觸發時需要呼叫的方法。
(2) 通過C# event關鍵字用相關委託宣告這個事件。
就以上面我們舉得例子來寫一個簡單的事件吧。

namespace MyFirstEvent
{
    public delegate void BoilHandler(int p); //宣告委託

    public
class Heater { private int temperature; public event BoilHandler BoilEvent; //宣告事件 public void BoilWater() { for (int i = 0; i <= 100; ++i) { temperature = i; if (temperature >= 95) { //呼叫在該事件上註冊的方法
if (BoilEvent != null) BoilEvent(temperature); } } } } public class Alarm { public void MakeAlert(int t) { Console.WriteLine("The temperature of water is {0} ℃", t); } } class Program { static void Main(string[] args) { Heater h = new Heater(); Alarm a = new Alarm(); h.BoilEvent += a.MakeAlert; //註冊方法 h.BoilWater(); } } }``` 輸出結果:

The temperature of water is 95 ℃
The temperature of water is 96 ℃
The temperature of water is 97 ℃
The temperature of water is 98 ℃
The temperature of water is 99 ℃
The temperature of water is 100 ℃


####6. 為什麼要用事件?
從上面的例子看來事件和委託差不多嘛,上面的事情用委託完全可以實現,跟我們之前將的例子類似,把上面宣告的事件物件換成委託物件不就可以了嗎?為什麼又大費周折地引入事件這麼個東西呢?

那我們就來看看直接使用委託物件有什麼問題吧。如果我們沒有把委託成員變數定義為私有的,呼叫者就可以直接訪問委託物件。這樣,呼叫者不僅可以直接檢視委託物件上繫結的所有方法,還可以隨意地把變數重新賦值為新的委託物件。也就是說,在類中寫公共的委託成員會打破類的封裝,不僅會導致程式碼難以維護和除錯,還會帶來應用程式的安全風險。不要問我為什麼不把委託成員宣告稱私有的,如果宣告成私有的,那怎麼在外面把客戶端的方法註冊到委託呢?

既然直接用委託成員存在破壞封裝的問題,那為什麼事件可以解決這個問題呢?
`C#``event`關鍵字在編譯的時候會自動提供註冊和登出方法以及任何必要的委託型別成員的變數。這些委託成員變數總是宣告為私有的,所有觸發事件的物件不能直接訪問它們。

namespace MyFirstEvent
{
public delegate void BoilHandler(int p);

public class Heater 
{
    private int temperature;
    public BoilHandler BoilEvent;

    public void BoilWater()
    {
        for (int i = 0; i <= 100; ++i)
        {
            temperature = i;
            if (temperature >= 95)
            {
                if (BoilEvent != null) BoilEvent(temperature);
            }
        }
    }
}

public class Alarm
{
    public void MakeAlert(int t)
    {
        Console.WriteLine("The temperature of water is {0} ℃", t);
    }
}

static public class SomethingShouldNotHappen
{
    static public void Display(int t)
    {
        Console.WriteLine("Test temperature is {0} ℃", t);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Heater h = new Heater();
        Alarm a = new Alarm();

        h.BoilEvent += a.MakeAlert;

        // get invocation list and replace it
        foreach (var d in h.BoilEvent.GetInvocationList())
        {
            Console.WriteLine(d);
        }
        h.BoilEvent = SomethingShouldNotHappen.Display;

        h.BoilWater();
    }
}

}

輸出結果:(你看,我們輕易地看到了本不該看到的`invocation list`,並且對委託成員上繫結的方法進行了篡改)

MyFirstEvent.BoilHandler
Test temperature is 95 ℃
Test temperature is 96 ℃
Test temperature is 97 ℃
Test temperature is 98 ℃
Test temperature is 99 ℃
Test temperature is 100 ℃

如果我們用上面的`event`呢?
首先,`MyFirstEvent.Heater.BoilEvent`不提供公開的`GetInvocationList`方法。實際上它什麼公開的方法都不提供,你如果在`VS`裡面輸入`h.BoilEvent.`,你會發現`VS`根本就不會有任何方法提示。
其次,`event`擴充套件了兩個隱藏的公共方法,一個帶`add_`字首,一個帶`remove_`字首。使用的時候可以用`h.BoilEvent += x`或者`h.BoilEvent -= x`,你想直接用=是不可能編譯通過的,下面是錯誤資訊。

Error 1 The event ‘MyFirstEvent.Heater.BoilEvent’ can only appear on the left hand side of += or -= (except when used from within the type ‘MyFirstEvent.Heater’) c:\users\xxx\documents\visual studio 2012\Projects\MyFirstEvent\MyFirstEvent\Program.cs 54 15 MyFirstEvent


參考文獻:
《精通C#》
[C# 中的委託和事件](http://www.cnblogs.com/JimmyZhang/archive/2007/09/23/903360.html)
[Events Tutorial](https://msdn.microsoft.com/en-us/library/aa645739(v=vs.71).aspx)