談C#中的Delegate

引言
    Delegate是Dotnet1.0的時候已經存在的特性了,但由於在實際工作中一直沒有機會使用Delegate這個特性,所以一直沒有對它作整理。這兩天,我再度翻閱了一些關於Delegate的資料,並開始正式整理這個C#中著名的特性。本文將由淺入深的談一下Delegate這個特性。
一.Delegate是什麼?
    Delegate中文翻譯爲“委託”。Msdn中對Delegate的解釋如下:
    C#中的委託類似於C或C++中的函數指針。使用委託使程序員可以將方法引用封裝在委託對象內。然後可以將該委託對象傳遞給可調用所引用方法的代碼,而不必在編譯時知道將調用哪個方法。與C或C++中的函數指針不同,委託是面向對象、類型安全的,並且是安全的。
    如果你是第一次接觸Delegate這個概念,你可能會對上面這段文字感覺不知所云,不過不要緊,你可以先把Delegate認爲就是一個函數指針。
    而當你面對一個虛無的概念時,最好的應對方法就是直接看實例。下面一個簡單的Delegate使用例子。
class Program
    {
        static void OtherClassMethod(){
            Console.WriteLine("Delegate an other class's method");
        }
        static void Main(string[] args)
        {
            var test = new TestDelegate();
            test.delegateMethod = new TestDelegate.DelegateMethod(test.NonStaticMethod);
            test.delegateMethod += new TestDelegate.DelegateMethod(TestDelegate.StaticMethod);
            test.delegateMethod += Program.OtherClassMethod;
            test.RunDelegateMethods();
        }
    }
    class TestDelegate
    {
        public delegate void DelegateMethod();  //聲明瞭一個Delegate Type
        public DelegateMethod delegateMethod;   //聲明瞭一個Delegate對象
        public static void StaticMethod()   
        {
            Console.WriteLine("Delegate a static method");
        }
        public void NonStaticMethod()   
        {
            Console.WriteLine("Delegate a non-static method");
        }
        public void RunDelegateMethods()
        {
            if(delegateMethod != null){
                Console.WriteLine("---------");
                delegateMethod.Invoke();    
                   Console.WriteLine("---------");
            }
        }
    }
上面是一個Delegate的使用例子,運行看看結果吧。下面我稍微解釋一下:
【1】public delegate void DelegateMethod();這裏聲明瞭一個Delegate的類型,名爲DelegateMethod,這種Delegate類型可以搭載:返回值爲void,無傳入參數的函數。
【2】public DelegateMethod delegateMethod;這裏聲明瞭一個DelegateMethod的對象(即,聲明瞭某種Delegate類型的對象)。
區分:DelegateMethod是類型,delegateMethod是對象。
【3】爲什麼上面說Delegate可以看做是函數指針呢?看下面這段代碼:
test.delegateMethod = new TestDelegate.DelegateMethod(test.NonStaticMethod); 
test.delegateMethod += new TestDelegate.DelegateMethod(TestDelegate.StaticMethod); 
test.delegateMethod += Program.OtherClassMethod; 
這裏delegateMethod搭載了3個函數,而且可以通過調用delegateMethod.Invoke();運行被搭載的函數。這就是Delegate可以看作爲函數指針的原因。上面這段代碼中,delegateMethod只能搭載:返回值爲void,無傳入參數的函數(見:NonStaticMethod,StaticMethod,OtherClassMethod的定義),這和Delegate類型聲明有關(見DelegateMethod的聲明:public delegate void DelegateMethod())。
【4】Delegate在搭載多個方法時,可以通過+=增加搭載的函數,也可以通過-=來去掉Delegate中的某個函數。
二.Delegate和C++中函數指針的區別
    Delegate和C++中的函數指針很像,但如果深入對比,發現其實還是有區別的,區別主要有三個方面(參考Stanley B. Lippman的一篇文章)
      1) 一個 delegate對象一次可以搭載多個方法(methods),而不是一次一個。當我們喚起一個搭載了多個方法(methods)的delegate,所有方法以其“被搭載到delegate對象的順序”被依次喚起。
      2) 一個delegate對象所搭載的方法(methods)並不需要屬於同一個類別。一個delegate對象所搭載的所有方法(methods)必須具有相同的原型和形式。然而,這些方法(methods)可以即有static也有non-static,可以由一個或多個不同類別的成員組成。
      3) 一個delegate type的聲明在本質上是創建了一個新的subtype instance,該 subtype 派生自 .NET library framework 的 abstract base classes Delegate 或 MulticastDelegate,它們提供一組public methods用以詢訪delegate對象或其搭載的方法(methods) 與函數指針不同,委託是面向對象、類型安全並且安全的
    看完上面關於Delegate的介紹,相信大家對它也有所瞭解了,下面我們將進行更深入地討論!
三.Delegate什麼時候該用?
    看完上面的介紹,你可以會有一些疑問,爲什麼會有Delegate?實際中什麼時候會用到?什麼時候應該去用? 在回答這些問題之前,大家可以先看看下面這段代碼:
    class Program
    {
        static void Main(string[] args)
        {
            var car = new Car(15);
            new Alerter(car);
            car.Run(120);
        }
    }
    class Car
    {
        public delegate void Notify(int value);
        public event Notify notifier;
        private int petrol = 0;
        public int Petrol
        {
            get { return petrol; }
            set
            {
                petrol = value;
                if (petrol < 10)  //當petrol的值小於10時,出發警報
                {
                    if (notifier != null)
                    {
                        notifier.Invoke(Petrol);
                    }
                }
            }
        }
        public Car(int petrol)
        {
            Petrol = petrol;
        }
        public void Run(int speed)
        {
            int distance = 0;
            while (Petrol > 0)
            {
                Thread.Sleep(500);
                Petrol--;
                distance += speed;
                Console.WriteLine("Car is running... Distance is " + distance.ToString());
            }
        }
    }
    class Alerter
    {
        public Alerter(Car car)
        {
            car.notifier += new Car.Notify(NotEnoughPetrol);
        }
        public void NotEnoughPetrol(int value)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("You only have " + value.ToString() + " gallon petrol left!");
            Console.ResetColor();
        }
    }
看完了上面的代碼後,你可能會問:爲什麼不在public int Petrol中直接調用Alerter.NotEnoughPetrol呢?因爲Car模塊和Alerter模塊本身是兩個獨立的子系統,如果直接調用,耦合性就會增加,這不是我們願意看到的。
    其實以上的代碼是設計模式中的觀察者模式(觀察者模式又稱Source/Listener模式)的實現,當汽車在運行中汽油量<10時,警報器便會發出警報。在上面代碼中,Delegate相當於一個存放回調函數的函數指針,使用Delegate,我們可以非常方便地實現觀察者模式。而其實,在需要使用回調函數時,我們都可以考慮使用Delegate
    不知道你有沒有發現在上面的代碼中還有一個問題呢?
public event Notify notifier;
上面的代碼中,我們定義了一個Event,而事實上:
public Notify notifier;
這樣寫,也完全可以滿足我們的需求,這就引出了我們的另一個問題,Delegate和Event!
四.Delegate與Event
【1】Delegate和Event的關係
    看微軟的代碼時,我們會發現Delegate和Event這兩個關鍵字經常會一起出現!究竟他們是什麼關係呢?
    在Msdn中,有一段話描述Delegate和Event之間的關係,其實很簡單:
        聲明事件:若要在類內聲明事件,首先必須聲明該事件的委託類型。
【2】Delegate和Event配合使用的效果
    看下面幾幅圖,這是我從一個C#的Application程序截下來的:
 
從上圖看到,在響應圖形界面的操作中,我們用到了Event和Delegate,相信這也我們使用Event和Delegate最頻繁的地方了。這裏我還想羅嗦一下,平時需要我們自己寫代碼的界面事件響應函數,如:button_Click(…),其實都是回調函數,在自動生成的文件Form1.Designer.cs中,VS把事件和其對應的回調函數(即:button_Click(…)等)關聯起來,當觸發某事件時,對應的回調函數便會執行。
【3】“public Notify notifier”和“public event Notify notifier”的區別
    關於這個問題,我們直接ildasm看看IL代碼吧:>
“public Notify notifier”的IL代碼,如圖:
 
“public event Notify notifier”的IL代碼,如圖:
 
差別其實已經很明顯了,“public Notify notifier”相當於Class裏面的Field,訪問級別是public,而“public event Notify notifier”則相當於Property,訪問級別是private!由於以上的差別,他們在某些使用上,會稍有不同,詳細的可參考shensr寫的《delegate vs. event》。
五.Delegate中的Invoke與BeginInvoke方法
   簡單說一下,Invoke與BeginInvoke都是執行Delegate裏的搭載函數,而不同的是:Invoke是一個同步方法,BeginInvoke是一個異步方法。關於這個,有一篇文章《Invoke and BeginInvoke》,對此介紹的比較詳細,這裏就不多說了。
六.小結   
    回顧一下,到底什麼時候我們可能會用到Delegate:
【1】.當我們在C#中需要類似函數指針這樣東西時。
【2】.當我們需要使用回調函數的時候。
【3】.需要異步調用的時候。
【4】.實現觀察者模式的時候。
【5】.處理事件響應的時候。
    以上內容均爲個人看法,如果有錯漏,請各位及時指出:>
    轉載請說明出處,謝謝![hyddd(http://www.cnblogs.com/hyddd/)]
參考資料
【5】《delegate vs. event
【7】《C# Delegate 簡介
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章