參考文章
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();
}




