參考文章
1. 委托Delegate
C#中的Delegate對應於C中的指針,但是又有所不同C中的指針既可以指向方法,又可以指向變量,並且可以進行類型轉換,
C中的指針實際上就是內存地址變量,他是可以直接操作內存的,通過內存地址直接訪問變量,直接調用方法。
而C#中的Delegate是強類型的,也就是說在聲明委托時就已經指定了該變量只能指向具有特定參數,以及返回值的方法。
使用delegate就可以直接建立任何名稱的委托類型,當進行系統編譯時,系統就會自動生成此類型。您可以使用delegate void MyDelegate()
方式建立一個委托類,並使用ILDASM.exe觀察其成員。由ILDASM.exe 中可以看到,它繼承了System.MulticastDelegate類,
並自動生成BeginInvoke、EndInvoke、Invoke 等三個常用方法。
Invoke 方法是用於同步調用委托對象的對應方法,而BeginInvoke、EndInvoke是用於以異步方式調用對應方法的。
public class MyDelegate:MulticastDelegate { //同步調用委托方法 public virtual void Invoke(); //異步調用委托方法 public virtual IAsyncResult BeginInvoke(AsyncCallback callback,object state); public virtual void EndInvoke(IAsyncResult result); }
MulticastDelegate是System.Delegate的子類,它是一個特殊類,編譯器和其他工具可以從此類派生,但是自定義類不能顯式地從此類進行派生。它支持多路廣播委托,並擁有一個帶有鏈接的委托列表,在調用多路廣播委托時,系統將按照調用列表中的委托出現順序來同步調用這些委托。
MulticastDelegate具有兩個常用屬性:Method、Target。其中Method 用於獲取委托所表示的方法Target 用於獲取當前調用的類實例。
1.1 委托的使用
當建立委托對象時,委托的參數類型必須與委托方法相對應。只要向建立委托對象的構造函數中輸入方法名稱example.Method,委托就會直接綁定此方法。使用myDelegate.Invoke(string message),就能顯式調用委托方法。但在實際的操作中,我們無須用到 Invoke 方法,而只要直接使用myDelegate(string message),就能調用委托方法。
無返回值的委托
class Program { delegate void MyDelegate(string message); public class Example { public void Method(string message) { MessageBox.Show(message); } } static void Main(string[] args) { Example example=new Example(); MyDelegate myDelegate=new MyDelegate(example.Method); myDelegate("Hello World"); Console.ReadKey(); } }
有返回值的委托
class Program { delegate string MyDelegate(string message); public class Example { public string Method(string name) { return "Hello " + name; } } static void Main(string[] args) { Example example=new Example(); //綁定委托方法 MyDelegate myDelegate=new MyDelegate(example.Method); //調用委托,獲取返回值 string message = myDelegate("Leslie"); Console.WriteLine(message); Console.ReadKey(); } }
多路廣播委托
delegate double MyDelegate(double message); public class Price { public double Ordinary(double price) { double price1 = 0.95 * price; Console.WriteLine("Ordinary Price : "+price1); return price1; } public double Favourable(double price) { double price1 = 0.85 * price; Console.WriteLine("Favourable Price : " + price1); return price1; } static void Main(string[] args) { Price price = new Price(); //綁定Ordinary方法 MyDelegate myDelegate = new MyDelegate(price.Ordinary); //綁定Favourable方法 myDelegate += new MyDelegate(price.Favourable); //調用委托 Console.WriteLine("Current Price : " + myDelegate(100)); Console.ReadKey(); } }
輸出
1.2 委托的協變與逆變
前面已經說過,委托是強類型的方法指針,但是在面對具有繼承關系類型的參數、或者返回值時,委托是如何處理的呢。
協變(返回值類型具有繼承關系的方法)
public class Worker {.......} public class Manager:Worker {.......} class Program { public delegate Worker GetWorkerHandler(int id); //在 Framework2.0 以上,委托 GetWorkerHandler 可綁定 GetWorker 與 GetManager 兩個方法 public static Worker GetWorker(int id) { Worker worker = new Worker(); return worker; } public static Manager GetManager(int id) { Manager manager = new Manager(); return manager; } static void Main(string[] args) { GetWorkerHandler workerHandler = new GetWorkerHandler(GetWorker); Worker worker=workerHandler(1); GetWorkerHandler managerHandler = new GetWorkerHandler(GetManager); Manager manager = managerHandler(2) as Manager; Console.ReadKey(); } }
委托 GetWorkerHandler 可以綁定 GetWorker 與 GetManager 兩個方法
逆變
委托逆變,是指委托方法的參數同樣可以接收 “繼承” 這個傳統規則。像下面的例子,以 object 為參數的委托,可以接受任何 object 子類的對象作為參數。最后可以在處理方法中使用 is 對輸入數據的類型進行判斷,分別處理對不同的類型的對象。
class Program { public delegate void Handler(object obj); public static void GetMessage(object message) { if (message is string) Console.WriteLine("His name is : " + message.ToString()); if (message is int) Console.WriteLine("His age is : " + message.ToString()); } static void Main(string[] args) { Handler handler = new Handler(GetMessage); handler(29); Console.ReadKey(); } }
注意:委托與其綁定方法的參數必須一至,即當 Handler 所輸入的參數為 object 類型,其綁定方法 GetMessage 的參數也必須為 object 。否則,即使綁定方法的參數為 object 的子類,系統也無法辨認。
大家可能注意到了,這個委托方法GetMessage的實現不是那么優雅,於是泛型委托應運而生。
class Program { public delegate void Handler<T>(T obj); public static void GetWorkerWages(Worker worker) { Console.WriteLine("Worker's total wages is " + worker.Wages); } public static void GetManagerWages(Manager manager) { Console.WriteLine("Manager's total wages is "+manager.Wages); } static void Main(string[] args) { Handler<Worker> workerHander = new Handler<Worker>(GetWorkerWages); Worker worker = new Worker(); worker.Wages = 3000; workerHander(worker); Handler<Manager> managerHandler = new Handler<Manager>(GetManagerWages); Manager manager = new Manager(); manager.Wages = 4500; managerHandler(manager); Console.ReadKey(); } }
2. event事件的由來
事件是特殊的委托,他為委托提供了封裝性,一方面允許從類的外部增加,刪除綁定方法,另一方面又不允許從類的外部來觸發委托所綁定了方法。
public delegate double PriceHandler(); public class PriceManager { public PriceHandler GetPriceHandler; //委托處理,當價格高於100元按8.8折計算,其他按原價計算 public double GetPrice() { if (GetPriceHandler.GetInvocationList().Count() > 0) { if (GetPriceHandler() > 100) return GetPriceHandler()*0.88; else return GetPriceHandler(); } return -1; } } class Program { static void Main(string[] args) { PriceManager priceManager = new PriceManager(); //調用priceManager的GetPrice方法獲取價格 //直接調用委托的Invoke獲取價格,兩者進行比較 priceManager.GetPriceHandler = new PriceHandler(ComputerPrice); Console.WriteLine(string.Format("GetPrice\n Computer's price is {0}!", priceManager.GetPrice())); Console.WriteLine(string.Format("Invoke\n Computer's price is {0}!", priceManager.GetPriceHandler.Invoke())); Console.WriteLine(); priceManager.GetPriceHandler = new PriceHandler(BookPrice); Console.WriteLine(string.Format("GetPrice\n Book's price is {0}!", priceManager.GetPrice())); Console.WriteLine(string.Format("Invoke\n Book's price is {0}!" , priceManager.GetPriceHandler.Invoke())); Console.ReadKey(); } //書本價格為98元 public static double BookPrice() { return 98.0; } //計算機價格為8800元 public static double ComputerPrice() { return 8800.0; } }
以上代碼實現了對於100元以上商品的的88折處理。一方面為了給GetPriceHandler綁定方法就必須將委托聲明為public,但是一旦聲明為public
就可以在類外部直接通過Invoke來調用該委托所綁定的方法,而產生我們不需要的結果。
當然我們可以將GetPriceHandler聲明為private並且通過public 的addHandler,removeHandler來消除委托public的副作用,但是C#提供了更加優雅的方法:
那就是event關鍵字。
事件(event)可被視作為一種特別的委托,它為委托對象隱式地建立起add_XXX、remove_XXX 兩個方法,用作注冊與注銷事件的處理方法。而且事件對應的變量成員將會被視為 private 變量,外界無法超越事件所在對象直接訪問它們,這使事件具備良好的封裝性,而且免除了add_XXX、remove_XXX等繁瑣的代碼。
public class EventTest { public delegate void MyDelegate(); public event MyDelegate MyEvent; }
觀察事件的編譯過程可知,在編譯的時候,系統為 MyEvent 事件自動建立add_MyEvent、remove_MyEvent 方法。
事件能通過+=和-=兩個方式注冊或者注銷對其處理的方法,使用+=與-=操作符的時候,系統會自動調用對應的 add_XXX、remove_XXX 進行處理。
值得留意,在PersonManager類的Execute方法中,如果 MyEvent 綁定的處理方法不為空,即可使用MyEvent(string)引發事件。但如果在外界的 main 方法中直接使用 personManager.MyEvent (string) 來引發事件,系統將引發錯誤報告。這正是因為事件具備了良好的封裝性,使外界不能超越事件所在的對象訪問其變量成員。
注意:在事件所處的對象之外,事件只能出現在+=,-=的左方。
public delegate void MyDelegate(string name);
public class PersonManager
{
public event MyDelegate MyEvent;
//執行事件
public void Execute(string name)
{
if (MyEvent != null)
MyEvent(name);
}
}
class Program
{
static void Main(string[] args)
{
PersonManager personManager = new PersonManager();
//綁定事件處理方法
personManager.MyEvent += new MyDelegate(GetName);
personManager.Execute("Leslie");
Console.ReadKey();
}
public static void GetName(string name)
{
Console.WriteLine("My name is " + name);
}
}
在綁定事件處理方法的時候,事件出現在+=、-= 操作符的左邊,對應的委托對象出現在+=、-= 操作符的右邊。對應以上例子,事件提供了更簡單的綁定方式,只需要在+=、-= 操作符的右方寫上方法名稱,系統就能自動辯認。
public delegate void MyDelegate(string name); public class PersonManager { public event MyDelegate MyEvent; ......... } class Program { static void Main(string[] args) { PersonManager personManager = new PersonManager(); //綁定事件處理方法 personManager.MyEvent += GetName; ............. } public static void GetName(string name) {.........} }
如果覺得編寫 GetName 方法過於麻煩,你還可以使用匿名方法綁定事件的處理。
public delegate void MyDelegate(string name); public class PersonManager { public event MyDelegate MyEvent; //執行事件 public void Execute(string name) { if (MyEvent != null) MyEvent(name); } static void Main(string[] args) { PersonManager personManager = new PersonManager(); //使用匿名方法綁定事件的處理 personManager.MyEvent += delegate(string name){ Console.WriteLine("My name is "+name); }; personManager.Execute("Leslie"); Console.ReadKey(); } }
3. lambda表達式
在Framework 2.0 以前,聲明委托的唯一方法是通過方法命名,從Framework 2.0 起,系統開始支持匿名方法。
通過匿名方法,可以直接把一段代碼綁定給事件,因此減少了實例化委托所需的編碼系統開銷。
而在 Framework 3.0 開始,Lambda 表達式開始逐漸取代了匿名方法,作為編寫內聯代碼的首選方式。總體來說,Lambda 表達式的作用是為了使用更簡單的方式來編寫匿名方法,徹底簡化委托的使用方式。
使用匿名方法
static void Main(string[] args) { Button btn = new Button(); btn.Click+=delegate(object obj,EventArgs e){ MessageBox.Show("Hello World !"); }; }
使用lambda表達式
static void Main(string[] args) { Button btn = new Button(); btn.Click+=(object obj,EventArgs e)=>{ MessageBox.Show("Hello World !"); }; }
3.1常用泛型委托
public delegate bool Predicate<T>(T obj)
它是一個返回bool的泛型委托,能接受一個任意類型的對象作為參數。
class Program { static void Main(string[] args) { List<Person> list = GetList(); //綁定查詢條件 Predicate<Person> predicate = new Predicate<Person>(Match); List<Person> result = list.FindAll(predicate); Console.WriteLine(“Person count is : ” + result.Count); Console.ReadKey(); } //模擬源數據 static List<Person> GetList() { var personList = new List<Person>(); var person1 = new Person(1,"Leslie",29); personList.Add(person1); ........ return personList; } //查詢條件 static bool Match(Person person) { return person.Age <= 30; } } public class Person { public Person(int id, string name, int age) { ID = id; Name = name; Age = age; } public int ID { get; set; } public string Name { get; set; } public int Age { get; set; } }
Action<T> 的使用方式與 Predicate<T> 相似,不同之處在於 Predicate<T> 返回值為 bool , Action<T> 的返回值為 void。
Action 支持0~16個參數,可以按需求任意使用。
public delegate void Action()
public delegate void Action<T1>(T1 obj1)
public delegate void Action<T1,T2> (T1 obj1, T2 obj2)
public delegate void Action<T1,T2,T3> (T1 obj1, T2 obj2,T3 obj3)
............
public delegate void Action<T1,T2,T3,......,T16> (T1 obj1, T2 obj2,T3 obj3,......,T16 obj16)
static void Main(string[] args) { Action<string> action=ShowMessage; action("Hello World"); Console.ReadKey(); } static void ShowMessage(string message) { MessageBox.Show(message); }
委托 Func 與 Action 相似,同樣支持 0~16 個參數,不同之處在於Func 必須具有返回值
public delegate TResult Func<TResult>()
public delegate TResult Func<T1,TResult>(T1 obj1)
public delegate TResult Func<T1,T2,TResult>(T1 obj1,T2 obj2)
public delegate TResult Func<T1,T2,T3,TResult>(T1 obj1,T2 obj2,T3 obj3)
............
public delegate TResult Func<T1,T2,T3,......,T16,TResult>(T1 obj1,T2 obj2,T3 obj3,......,T16 obj16)
static void Main(string[] args) { Func<double, bool, double> func = Account; double result=func(1000, true); Console.WriteLine("Result is : "+result); Console.ReadKey(); } static double Account(double a,bool condition) { if (condition) return a * 1.5; else return a * 2; }
3.2 lambda表達式
Lambda 的表達式的編寫格式如下:
x=> x * 1.5
當中 “ => ” 是 Lambda 表達式的操作符,在左邊用作定義一個參數列表,右邊可以操作這些參數。
例子一, 先把 int x 設置 1000,通過 Action 把表達式定義為 x=x+500 ,最后通過 Invoke 激發委托。
static void Main(string[] args)
{
int x = 1000;
Action action = () => x = x + 500;
action.Invoke();
Console.WriteLine("Result is : " + x);
Console.ReadKey();
}
例子二,通過 Action<int> 把表達式定義 x=x+500, 到最后輸入參數1000,得到的結果與例子一相同。
注意,此處Lambda表達式定義的操作使用 { } 括弧包括在一起,里面可以包含一系列的操作。
static void Main(string[] args)
{
Action<int> action = (x) =>
{
x = x + 500;
Console.WriteLine("Result is : " + x);
};
action.Invoke(1000);
Console.ReadKey();
}