程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 委托和事件 (1),委托事件

委托和事件 (1),委托事件

編輯:C#入門知識

委托和事件 (1),委托事件


個人認為,c#最重要的精髓在於其委托。

說實話現在已經是c#5.0的時代,c#6很快也要出來了,委托作為一個c#1就有的性質,已經早就被更高級的工具例如泛型委托,lambda表達式包裝起來了,基本上已經很少有人會在程序中聲明一個delegate。不過,了解一下基礎也是很好的,

基本概念

委托是一個特殊的類(密封類),可以被視為函數指針,其代表一類和委托簽名的輸入輸出變量類型和個數相同的方法。委托本身可以作為變量傳入方法。

借用經典的greetPeople例子,在實際工作中,總會遇到類似的情況,即通過switch來對不同的輸入執行不同的結果。但我們看到,其實每個switch執行的方法都很類似,方法的簽名還完全相同。此時我們很容易想到的就是當再加入一個新的switch case的時候,我們除了要加一個新方法之外,還要對現成的GreetPeople方法進行修改,這違反了開閉原則(對修改關閉)。有沒有一種方法,可以在不修改GreetPeople方法的前提下對程序進行擴展呢?

public class Program
    {
        public static void Main()
        {
            GreetPeople("Alex", "Chinese");
            GreetPeople("Beta", "English");
            GreetPeople("Clara", "France");

            Console.ReadKey();
        }

        public static void GreetPeople(string name, string lang)
        {
            switch (lang)
            {
                case "English":
                    EnglishGreeting(name);
                    break;
                case "Chinese":
                    ChineseGreeting(name);
                    break;
                case "France":
                    FrenchGreeting(name);
                    break;
            }
        }

        public static void EnglishGreeting(string name)
        {
            Console.WriteLine("Morning, " + name);
        }

        public static void ChineseGreeting(string name)
        {
            Console.WriteLine("早上好, " + name);
        }

        public static void FrenchGreeting(string name)
        {
            Console.WriteLine("Bonjour, " + name);
        }
    }

首先,我們要放棄使用switch,否則我們終究避免不了修改GreetPeople方法的命運。之後,我們自然而然的會想,假設我們在主函數裡面傳入的第二變量不是字符串,而是方法名,那麼似乎我們就不需要那個switch了。因為我們會直接去到對應的方法,不用switch再分派過去。那麼這件事該怎麼實現呢?傳入方法名到底意味著什麼呢?這些方法的簽名全都一樣,我是否可以用某種手法將他們封裝起來呢

於是,委托就出現了,它可以解決上面我們所有的問題。委托代表了一類具有相同簽名的方法,可以變身為其中任何一個。委托也可以作為變量傳入方法,其行為和其他類型例如int,string完全一樣。很多人覺得委托很不好理解,是因為委托代表的是方法,而普通類型代表的都是值或者對象。比如string,其可以代表任何的字符串,int也是可以代表在某個取值范圍中任何的整數一樣。委托則代表著某一類方法(視其定義而定),當某個函數的其中一個變量是委托時,意味著我們將要傳入一個可以被該委托所代表的方法名。委托是方法的指針,可以指向不同的方法,類比一下,如同string可以指向堆上的字符串,int可以指向棧上的整數一樣。

public class Program
    {
        //現在這個委托代表了一類輸入一個字符串,沒有輸出的方法
        public delegate void GreetPeopleDelegate(string name);

        public static void Main()
        {
            //利用委托,傳入不同的方法會得到不同的結果
            GreetPeople("Alex", ChineseGreeting);
            GreetPeople("Beta", EnglishGreeting);
            GreetPeople("Clara", FrenchGreeting);

            Console.ReadKey();
        }

        //委托可以作為方法的變量,從而代替switch
        public static void GreetPeople(string name, GreetPeopleDelegate aGreetPeopleDelegate)
        {
            aGreetPeopleDelegate(name);
        }

        public static void EnglishGreeting(string name)
        {
            Console.WriteLine("Morning, " + name);
        }

        public static void ChineseGreeting(string name)
        {
            Console.WriteLine("早上好, " + name);
        }

        public static void FrenchGreeting(string name)
        {
            Console.WriteLine("Bonjour, " + name);
        }
    }

 

 

委托的方法和屬性

1. MulticastDelegate(委托自己所在的密封類)

小寫的delegate是你用來聲明委托的關鍵字,當你聲明完之後,編譯器創建一個新的密封類,該類的類型是MulticastDelegate(繼承自System.MultipleDelegate,其再繼承自System.Delegate)這就是大寫的和小寫d的delegate關鍵字的區別。

這個新的密封類定義了三個方法,invoke, begininvoke和endinvoke。invoke是當你調用委托所代表的方法時隱式執行的,例如aGreetPeopleDelegate(name)實際上和aGreetPeopleDelegate.Invoke(name)沒有區別。所以Invoke的方法簽名永遠和委托本身相同,即如果某委托簽名為int a(int x, int y)則它的invoke簽名一定是public int Invoke(int x, int y)。

後兩者則賦予委托異步的能力。這兩個方法放到多線程系列中進行分析。

2. System.MultipleDelegate和委托的調用列表(方法鏈)

System.MultipleDelegate中重要的方法GetInvocationList()獲得當前委托所代表的方法的各種信息。注意這個方法返回的是一個數組,這也就是說,委托可以同時代表多個方法(此時,invoke委托會將該組方法順序一個一個執行),這也叫做委托的多路廣播。通過+=和-=,我們可以為委托增加和減少方法。我們無需深入研究方法鏈是如何實現的,但以下幾個事情需要知道:

1. 可以重復增加相同的方法,此時該方法將執行兩次

2. 可以刪除委托所有的方法,即委托可以暫時不代表方法,此時invoke委托將什麼都不發生

3. 即使不小心多刪除了方法一次,也不會出現異常(如增加了一個方法然後誤刪除了兩次),此時委托暫時不代表任何方法

4. +=和-=是操作符的重載,本質是調用System.Delegate中的Combine和Remove方法

System.MultipleDelegate還重載了==和!=,判斷兩個委托是否相等僅僅看它們代表的方法鏈是否相等(即都是指向相同對象上的相同方法)。

3. System.Delegate

System.Delegate中有兩個重要的公共成員target和method。其中method代表方法的信息,而如果Method代表一個靜態成員,則Target為null,否則,target代表方法所在的對象。通過GetInvocationList()我們可以查看當前委托中方法鏈的信息。另外這個類還有Combine和Remove方法,其已經被子類重載故不需要直接調用他們。

public class Program
    {
        public delegate void GreetPeopleDelegate(string name);

        public static void Main()
        {
            //實例化委托一定要為其指派一個符合要求的方法
            GreetPeopleDelegate aGreetPeopleDelegate = new GreetPeopleDelegate(ChineseGreeting);
            PrintInvocationList(aGreetPeopleDelegate.GetInvocationList());

            //增加一個方法
            aGreetPeopleDelegate += EnglishGreeting;
            PrintInvocationList(aGreetPeopleDelegate.GetInvocationList());

            anotherClass a = new anotherClass();
            //增加一個非靜態方法
            aGreetPeopleDelegate += a.NonStaticGreeting;
            PrintInvocationList(aGreetPeopleDelegate.GetInvocationList());

            Console.ReadKey();
        }

        //觀看當前委托中代表的方法鏈
        public static void PrintInvocationList(Delegate[] aList)
        {
            foreach (var delegateMethod in aList)
            {
                //Method代表當前維護的方法的詳細信息
                //如果Method代表一個靜態成員,則Target為null,否則,target代表方法所在的對象
                Console.WriteLine(string.Format("Method name: {0}, value: {1}", delegateMethod.Method, delegateMethod.Target));
            }
            Console.WriteLine("------------------------------------");
        }

        public static void EnglishGreeting(string name)
        {
            Console.WriteLine("Morning, " + name);
        }

        public static void ChineseGreeting(string name)
        {
            Console.WriteLine("早上好, " + name);
        }

        public static void FrenchGreeting(string name)
        {
            Console.WriteLine("Bonjour, " + name);
        }
    }

    public class anotherClass
    {
        public void NonStaticGreeting(string name)
        {
            Console.WriteLine("Bonjour, " + name);
        }
    }

動態維護委托的調用列表

上面說了委托都是有一個調用列表的,我們可以動態的操作他,為他添加或者刪除成員。如果我們創建一個公共的委托成員列表,則可以很容易的實現多路廣播。下面例子來自精通c#第六版。其中調用列表

public CarEngineHandler methodList;

 是公共的,並且外部方法main會創建一個新的實例作為訂閱者,在適當情形下,調用委托然後執行委托列表中的方法。

public class Program
    {
        public static void Main()
        {
            //創建了一個新的訂閱者
            var c = new Car("Mycar", 0, 100);

            //該訂閱者(消費者)訂閱了方法OnCarEvent1
            c.methodList += OnCarEvent1;

            //取消注釋實現多路廣播,此時將會執行兩個方法
            //c.methodList += OnCarEvent2;

            for (int i = 0; i < 10; i++)
            {
                c.Accel(20);
            }

            Console.ReadKey();
        }

        public static void OnCarEvent1(string msg)
        {
            Console.WriteLine("***** message from car *****");
            Console.WriteLine("=> " + msg);
            Console.WriteLine("****************************");
        }

        public static void OnCarEvent2(string msg)
        {
            Console.WriteLine("=> " + msg.ToUpper());
        }
    }

    public class Car
    {
        public string name { get; set; }
        public int currentSpeed { get; set; }
        public int MaxSpeed { get; set; }

        private bool isDead { get; set; }

        public delegate void CarEngineHandler(string message);

        public CarEngineHandler methodList;

        public Car(string name, int currentSpeed, int MaxSpeed)
        {
            this.name = name;
            this.currentSpeed = currentSpeed;
            this.MaxSpeed = MaxSpeed;
            this.isDead = false;
        }

        public void Accel(int delta)
        {
            //死亡時執行訂閱列表中的方法
            if (isDead)
            {
                if (methodList != null)
                    methodList("Sorry, car is broken");
            }
            else
            {
                currentSpeed += delta;
                if (currentSpeed >= MaxSpeed) isDead = true;
                else Console.WriteLine("Current speed: " + currentSpeed);
            }
        }
    }

從委托到事件

上個例子中的委托有一個問題,就是其不夠安全。調用者可以直接訪問委托對象CarEngineHandler,並且還能對其調用列表:

1 invoke,即可以隨時使用委托

2 +=或者-=,甚至直接賦值(=)也可以

有時候,我們並不希望用戶可以更改委托的成員。而且,我們希望委托不能被用戶Invoke,而是在特定的時候被委托的訂閱者調用。也就是說我們希望下面兩句代碼都不通過編譯:

//為委托賦以一個全新的對象(我們不希望其他代碼可以改變委托指向)
c.methodList = OnCarEvent1;
            
//直接調用委托(我們不希望其他代碼可以直接調用,除非經過許可)
c.methodList.Invoke("test");

此時,一個自然的想法就是將委托本身定義為private,但如果這樣做,外部的所有類都無法使用該委托。所以我們還要搞若干公共的方法,作為外部類使用內部私有委托的橋梁。下面代碼中,methodList是私有的所以我們不能直接對他操作,我們要通過Car類的兩個公共方法操作他。(無關的代碼已省略)

public class Program
    {
        public static void Main()
        {
            //創建了一個新的訂閱者
            var c = new Car("Mycar", 0, 100);
c.Addmethod(OnCarEvent1); c.Invoke("test"); } public class Car {public delegate void CarEngineHandler(string message); private CarEngineHandler methodList; public CarEngineHandler Addmethod(CarEngineHandler aMethod) { methodList += aMethod; return methodList; } public void Invoke(string msg) { methodList.Invoke(msg); } }

但問題就來了,那對於所有的委托,如果我們要追求安全,豈不是都要弄這些方法,而且方法還比較多,有添加方法,刪除方法,方法的同步和異步的調用等。這看上去非常麻煩,要打很多的代碼。相信這時候你也想到了,又有一個強大的東西要出場了,它可以解決上面所有的問題,它就是事件。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved