說實話現在已經是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); } }
新聞熱點
疑難解答