引言
本篇文章將為你介紹一下 Delegate 的使用方式,,逐漸揭開 C# 當(dāng)中事件(Event)的由來,它能使處理委托類型的過程變得更加簡單,。 還將為您解釋委托的協(xié)變與逆變,,以及如何使用 Delegate 使 Observer(觀察者)模式的使用變得更加簡單,。 在事件的介紹上,會講述事件的使用方式,,并以ASP.NET的用戶控件為例子,,介紹一下自定義事件的使用。 最后一節(jié),,將介紹Predicate<T>,、Action<T>、Func<T,,TResult>多種泛型委托的使用和Lambda的發(fā)展過程與其使用方式,。 因為時間倉促,文中有錯誤的地方敬請點(diǎn)評,。
目錄
一,、委托類型的來由
二、建立委托類
三,、委托使用方式
四,、深入解析事件
五、Lambda 表達(dá)式
一,、委托類型的來由
記得在使用C語言的年代,,整個項目中都充滿著針指的身影,那時候流行使用函數(shù)指針來創(chuàng)建回調(diào)函數(shù),,使用回調(diào)可以把函數(shù)回調(diào)給程序中的另一個函數(shù),。但函數(shù)指針只是簡單地把地址指向另一個函數(shù),并不能傳遞其他額外信息,。 在.NET
中,,在大部分時間里都沒有指針的身影,因為指針被封閉在內(nèi)部函數(shù)當(dāng)中,??墒腔卣{(diào)函數(shù)卻依然存在,,它是以委托的方式來完成的,。委托可以被視為一個更高級的指
針,,它不僅僅能把地址指向另一個函數(shù),而且還能傳遞參數(shù),,返回值等多個信息,。系統(tǒng)還為委托對象自動生成了同步、異步的調(diào)用方式,,開發(fā)人員使用
BeginInvoke,、EndInvoke 方法就可以拋開 Thread 而直接使用多線程調(diào)用 。
回到目錄
二,、建立委托類
使用delegate就可以直接建立任何名稱的委托類型,,當(dāng)進(jìn)行系統(tǒng)編譯時,系統(tǒng)就會自動生
成此類型,。您可以使用delegate void MyDelegate()
方式建立一個委托類,,并使用ILDASM.exe觀察其成員。由ILDASM.exe
中可以看到,,它繼承了System.MulticastDelegate類,,并自動生成BeginInvoke、EndInvoke,、Invoke
等三個常用方法,。
Invoke 方法是用于同步調(diào)用委托對象的對應(yīng)方法,而BeginInvoke,、EndInvoke是用于以異步方式調(diào)用對應(yīng)方法的,。 對于異步調(diào)用的使用方式,可以參考:C#綜合揭秘——細(xì)說多線程
1 public class MyDelegate:MulticastDelegate 2 { 3 //同步調(diào)用委托方法 4 public virtual void Invoke(); 5 //異步調(diào)用委托方法 6 public virtual IAsyncResult BeginInvoke(AsyncCallback callback,object state); 7 public virtual void EndInvoke(IAsyncResult result); 8 }
MulticastDelegate是System.Delegate的子類,,它是一個特殊類,,編譯器和其他工具可以從此類派生,但是自定義類不能顯式地從此類進(jìn)行派生,。它支持多路廣播委托,,并擁有一個帶有鏈接的委托列表,在調(diào)用多路廣播委托時,,系統(tǒng)將按照調(diào)用列表中的委托出現(xiàn)順序來同步調(diào)用這些委托,。
MulticastDelegate具有兩個常用屬性:Method、Target,。其中Method 用于獲取委托所表示的方法Target 用于獲取當(dāng)前調(diào)用的類實例,。
MulticastDelegate有以下幾個常用方法:
方法名稱 | 說明 |
Clone |
創(chuàng)建委托的淺表副本。 |
GetInvocationList |
按照調(diào)用順序返回此多路廣播委托的調(diào)用列表,。 |
GetMethodImpl |
返回由當(dāng)前的 MulticastDelegate 表示的靜態(tài)方法,。 |
GetObjectData |
用序列化該實例所需的所有數(shù)據(jù)填充 SerializationInfo 對象。 |
MemberwiseClone |
創(chuàng)建當(dāng)前 Object 的淺表副本。 |
RemoveImpl |
調(diào)用列表中移除與指定委托相等的元素 |
MulticastDelegate與Delegate給委托對象建立了強(qiáng)大的支持,,下面向各位詳細(xì)介紹一下委托的使用方式,。
回到目錄
三、委托使用方式
3.1 簡單的委托
當(dāng)建立委托對象時,,委托的參數(shù)類型必須與委托方法相對應(yīng),。只要向建立委托對象的構(gòu)造函數(shù)中輸入方法名稱example.Method,委托就會直接
綁定此方法,。使用myDelegate.Invoke(string message),,就能顯式調(diào)用委托方法。但在實際的操作中,,我們無須用到
Invoke 方法,,而只要直接使用myDelegate(string message),就能調(diào)用委托方法,。
1 class Program 2 { 3 delegate void MyDelegate(string message); 4 5 public class Example 6 { 7 public void Method(string message) 8 { 9 MessageBox.Show(message); 10 } 11 } 12 13 static void Main(string[] args) 14 { 15 Example example=new Example(); 16 MyDelegate myDelegate=new MyDelegate(example.Method); 17 myDelegate("Hello World"); 18 Console.ReadKey(); 19 } 20 }
3.2 帶返回值的委托
當(dāng)建立委托對象時,,委托的返回值必須與委托方法相對應(yīng)。使用下面的例子,,方法將返回 “Hello Leslie” ,。
1 class Program 2 { 3 delegate string MyDelegate(string message); 4 5 public class Example 6 { 7 public string Method(string name) 8 { 9 return "Hello " + name; 10 } 11 } 12 13 static void Main(string[] args) 14 { 15 Example example=new Example(); 16 //綁定委托方法 17 MyDelegate myDelegate=new MyDelegate(example.Method); 18 //調(diào)用委托,獲取返回值 19 string message = myDelegate("Leslie"); 20 Console.WriteLine(message); 21 Console.ReadKey(); 22 } 23 }
3.3 多路廣播委托
在第二節(jié)前曾經(jīng)提過,,委托類繼承于MulticastDelegate,,這使委托對象支持多路廣播,即委托對象可以綁定多個方法,。當(dāng)輸入?yún)?shù)后,,每個方法會按順序進(jìn)行迭代處理,并返回最后一個方法的計算結(jié)果,。 下
面的例子中,,Price 類中有兩個計算方法,Ordinary 按普通的9.5折計算,,F(xiàn)avourable 按優(yōu)惠價 8.5
折計算,。委托同時綁定了這兩個方法,在輸入?yún)?shù)100以后,,Ordinary,、Favourable這兩個方法將按順序迭代執(zhí)行下去,最后返回
Favourable 方法的計算結(jié)果 85,。
1 delegate double MyDelegate(double message); 2 3 public class Price 4 { 5 public double Ordinary(double price) 6 { 7 double price1 = 0.95 * price; 8 Console.WriteLine("Ordinary Price : "+price1); 9 return price1; 10 } 11 12 public double Favourable(double price) 13 { 14 double price1 = 0.85 * price; 15 Console.WriteLine("Favourable Price : " + price1); 16 return price1; 17 } 18 19 static void Main(string[] args) 20 { 21 Price price = new Price(); 22 //綁定Ordinary方法 23 MyDelegate myDelegate = new MyDelegate(price.Ordinary); 24 //綁定Favourable方法 25 myDelegate += new MyDelegate(price.Favourable); 26 //調(diào)用委托 27 Console.WriteLine("Current Price : " + myDelegate(100)); 28 Console.ReadKey(); 29 } 30 }
運(yùn)行結(jié)果
3.4 淺談Observer模式
回顧一下簡單的 Observer 模式,,它使用一對多的方式,可以讓多個觀察者同時關(guān)注同一個事物,,并作出不同的響應(yīng),。 例如下面的例
子,,Manager的底薪為基本工資的1.5倍,Assistant的底薪為基本工資的1.2倍,。WageManager類的
RegisterWorker方法與RemoveWorker方法可以用于注冊和注銷觀察者,,最后執(zhí)行Execute方法可以對多個已注冊的觀察者同時輸
入?yún)?shù)。
1 public class WageManager 2 { 3 IList<Worker> workerList = new List<Worker>(); 4 5 public void RegisterWorker(Worker worker) 6 { 7 workerList.Add(worker); 8 } 9 10 public void RemoveWorker(Worker worker) 11 { 12 workerList.Remove(worker); 13 } 14 15 public void Excute(double basicWages) 16 { 17 if (workerList.Count != 0) 18 foreach (var worker in workerList) 19 worker.GetWages(basicWages); 20 } 21 22 static void Main(string[] args) 23 { 24 WageManager wageManager = new WageManager(); 25 //注冊觀察者 26 wageManager.RegisterWorker(new Manager()); 27 wageManager.RegisterWorker(new Assistant()); 28 //同時輸入底薪3000元,,分別進(jìn)行計算 29 wageManager.Excute(3000); 30 31 Console.ReadKey(); 32 } 33 } 34 35 public abstract class Worker 36 { 37 public abstract double GetWages(double basicWages); 38 } 39 40 public class Manager:Worker 41 { 42 //Manager實際工資為底薪1.5倍 43 public override double GetWages(double basicWages) 44 { 45 double totalWages = 1.5 * basicWages; 46 Console.WriteLine("Manager's wages is " + totalWages); 47 return totalWages; 48 } 49 } 50 51 public class Assistant : Worker 52 { 53 //Assistant實際工資為底薪的1.2倍 54 public override double GetWages(double basicWages) 55 { 56 double totalWages = 1.2 * basicWages; 57 Console.WriteLine("Assistant's wages is " + totalWages); 58 return totalWages; 59 } 60 }
運(yùn)行結(jié)果
開發(fā) Observer
模式時借助委托,可以進(jìn)一步簡化開發(fā)的過程,。由于委托對象支持多路廣播,,所以可以把Worker類省略,。在WageManager類中建立了一個委托對象
wageHandler,,通過Attach與Detach方法可以分別加入或取消委托。如果觀察者想對事物進(jìn)行監(jiān)測,,只需要加入一個委托對象即可,。記得在
第二節(jié)曾經(jīng)提過,,委托的GetInvodationList方法能獲取多路廣播委托列表,在Execute方法中,,就是通過去多路廣播委托列表去判斷所綁
定的委托數(shù)量是否為0,。
1 public delegate double Handler(double basicWages); 2 3 public class Manager 4 { 5 public double GetWages(double basicWages) 6 { 7 double totalWages=1.5 * basicWages; 8 Console.WriteLine("Manager's wages is : " + totalWages); 9 return totalWages; 10 } 11 } 12 13 public class Assistant 14 { 15 public double GetWages(double basicWages) 16 { 17 double totalWages = 1.2 * basicWages; 18 Console.WriteLine("Assistant's wages is : " + totalWages); 19 return totalWages; 20 } 21 } 22 23 public class WageManager 24 { 25 private Handler wageHandler; 26 27 //加入觀察者 28 public void Attach(Handler wageHandler1) 29 { 30 wageHandler += wageHandler1; 31 } 32 33 //刪除觀察者 34 public void Detach(Handler wageHandler1) 35 { 36 wageHandler -= wageHandler1; 37 } 38 39 //通過GetInvodationList方法獲取多路廣播委托列表,如果觀察者數(shù)量大于0即執(zhí)行方法 40 public void Execute(double basicWages) 41 { 42 if (wageHandler!=null) 43 if(wageHandler.GetInvocationList().Count() != 0) 44 wageHandler(basicWages); 45 } 46 47 static void Main(string[] args) 48 { 49 WageManager wageManager = new WageManager(); 50 //加入Manager觀察者 51 Manager manager = new Manager(); 52 Handler managerHandler = new Handler(manager.GetWages); 53 wageManager.Attach(managerHandler); 54 55 //加入Assistant觀察者 56 Assistant assistant = new Assistant(); 57 Handler assistantHandler = new Handler(assistant.GetWages); 58 wageManager.Attach(assistantHandler); 59 60 //同時加入底薪3000元,,分別進(jìn)行計算 61 wageManager.Execute(3000); 62 Console.ReadKey(); 63 } 64 }
最后運(yùn)行結(jié)果與上面的例子相同,。
3.5 委托的協(xié)變與逆變
在 Framework 2.0
出現(xiàn)之前,委托協(xié)變這個概念還沒有出現(xiàn),。此時因為委托是安全類型,,它們不遵守繼承的基礎(chǔ)規(guī)則。即會這下面的情況:Manager 雖然是 Worker
的子類,,但 GetWorkerHander 委托不能直接綁定 GetManager 方法,,因為在委托當(dāng)中它們的返回值 Manager 與
Worker 被視為完全無關(guān)的兩個類型。
1 public class Worker 2 {.......} 3 public class Manager:Worker 4 {.......} 5 6 class Program 7 { 8 public delegate Worker GetWorkerHandler(int id); 9 public delegate Manager GetManagerHandler(int id); 10 11 public static Worker GetWorker(int id) 12 { 13 Worker worker = new Worker(); 14 .............. 15 return worker; 16 } 17 18 public static Manager GetManager(int id) 19 { 20 Manager manager = new Manager(); 21 .............. 22 return manager; 23 } 24 25 static void Main(string[] args) 26 { 27 GetWorkerHandler workerHandler = new GetWorkerHandler(GetWorker); 28 var worker=workerHandler(1); 29 30 GetManagerHandler managerHandler = new GetManagerHandler(GetManager); 31 var manager = managerHandler(2); 32 Console.ReadKey(); 33 } 34 }
自從Framework 2.0 面試以后,,委托協(xié)變的概念就應(yīng)運(yùn)而生,,此時委托可以按照傳統(tǒng)的繼承規(guī)則進(jìn)行轉(zhuǎn)換。即 GetWorkerHandler 委托可以直接綁定 GetManager 方法,。
1 public class Worker 2 {.......} 3 public class Manager:Worker 4 {.......} 5 6 class Program 7 { 8 public delegate Worker GetWorkerHandler(int id); 9 //在 Framework2.0 以上,,委托 GetWorkerHandler 可綁定 GetWorker 與 GetManager 兩個方法 10 11 public static Worker GetWorker(int id) 12 { 13 Worker worker = new Worker(); 14 return worker; 15 } 16 17 public static Manager GetManager(int id) 18 { 19 Manager manager = new Manager(); 20 return manager; 21 } 22 23 static void Main(string[] args) 24 { 25 GetWorkerHandler workerHandler = new GetWorkerHandler(GetWorker); 26 Worker worker=workerHandler(1); 27 GetWorkerHandler managerHandler = new GetWorkerHandler(GetManager); 28 Manager manager = managerHandler(2) as Manager; 29 Console.ReadKey(); 30 } 31 }
委托逆變,是指委托方法的參數(shù)同樣可以接收 “繼承” 這個傳統(tǒng)規(guī)則,。像下面的例子,,以 object 為參數(shù)的委托,可以接受任何 object 子類的對象作為參數(shù),。最后可以在處理方法中使用 is 對輸入數(shù)據(jù)的類型進(jìn)行判斷,,分別處理對不同的類型的對象,。
1 class Program 2 { 3 public delegate void Handler(object obj); 4 5 public static void GetMessage(object message) 6 { 7 if (message is string) 8 Console.WriteLine("His name is : " + message.ToString()); 9 if (message is int) 10 Console.WriteLine("His age is : " + message.ToString()); 11 } 12 13 static void Main(string[] args) 14 { 15 Handler handler = new Handler(GetMessage); 16 handler(29); 17 Console.ReadKey(); 18 } 19 }
運(yùn)行結(jié)果
注意:委托與其綁定方法的參數(shù)必須一至,即當(dāng) Handler 所輸入的參數(shù)為 object 類型,,其綁定方法 GetMessage 的參數(shù)也必須為 object ,。否則,即使綁定方法的參數(shù)為 object 的子類,,系統(tǒng)也無法辨認(rèn),。
3.6 泛型委托
委托逆變雖然實用,但如果都以 object 作為參數(shù),,則需要每次都對參數(shù)進(jìn)行類型的判斷,,這不禁令人感到厭煩。 為此,,泛型委托應(yīng)運(yùn)而生,,泛型委托有著委托逆變的優(yōu)點(diǎn),同時利用泛型的特性,,可以使一個委托綁定多個不同類型參數(shù)的方法,,而且在方法中不需要使用 is 進(jìn)行類型判斷,從而簡化了代碼,。
1 class Program 2 { 3 public delegate void Handler<T>(T obj); 4 5 public static void GetWorkerWages(Worker worker) 6 { 7 Console.WriteLine("Worker's total wages is " + worker.Wages); 8 } 9 10 public static void GetManagerWages(Manager manager) 11 { 12 Console.WriteLine("Manager's total wages is "+manager.Wages); 13 } 14 15 static void Main(string[] args) 16 { 17 Handler<Worker> workerHander = new Handler<Worker>(GetWorkerWages); 18 Worker worker = new Worker(); 19 worker.Wages = 3000; 20 workerHander(worker); 21 22 Handler<Manager> managerHandler = new Handler<Manager>(GetManagerWages); 23 Manager manager = new Manager(); 24 manager.Wages = 4500; 25 managerHandler(manager); 26 27 Console.ReadKey(); 28 } 29 }
運(yùn)行結(jié)果
回到目錄
四,、深入解析事件
4.1 事件的由來
在介紹事件之前大家可以先看看下面的例子, PriceManager 負(fù)責(zé)對商品價格進(jìn)行處理,,當(dāng)委托對象 GetPriceHandler 的返回值大于100元,,按8.8折計算,低于100元按原價計算,。
1 public delegate double PriceHandler(); 2 3 public class PriceManager 4 { 5 public PriceHandler GetPriceHandler; 6 7 //委托處理,,當(dāng)價格高于100元按8.8折計算,其他按原價計算 8 public double GetPrice() 9 { 10 if (GetPriceHandler.GetInvocationList().Count() > 0) 11 { 12 if (GetPriceHandler() > 100) 13 return GetPriceHandler()*0.88; 14 else 15 return GetPriceHandler(); 16 } 17 return -1; 18 } 19 } 20 21 class Program 22 { 23 static void Main(string[] args) 24 { 25 PriceManager priceManager = new PriceManager(); 26 27 //調(diào)用priceManager的GetPrice方法獲取價格 28 //直接調(diào)用委托的Invoke獲取價格,,兩者進(jìn)行比較 29 priceManager.GetPriceHandler = new PriceHandler(ComputerPrice); 30 Console.WriteLine(string.Format("GetPrice\n Computer's price is {0}!", 31 priceManager.GetPrice())); 32 Console.WriteLine(string.Format("Invoke\n Computer's price is {0}!", 33 priceManager.GetPriceHandler.Invoke())); 34 35 Console.WriteLine(); 36 37 priceManager.GetPriceHandler = new PriceHandler(BookPrice); 38 Console.WriteLine(string.Format("GetPrice\n Book's price is {0}!", 39 priceManager.GetPrice())); 40 Console.WriteLine(string.Format("Invoke\n Book's price is {0}!" , 41 priceManager.GetPriceHandler.Invoke())); 42 43 Console.ReadKey(); 44 } 45 //書本價格為98元 46 public static double BookPrice() 47 { 48 return 98.0; 49 } 50 //計算機(jī)價格為8800元 51 public static double ComputerPrice() 52 { 53 return 8800.0; 54 } 55 }
運(yùn)行結(jié)果
觀察運(yùn)行的結(jié)果,,如果把委托對象 GetPriceHandler 設(shè)置為 public ,外界可以直接調(diào)用 GetPriceHandler.Invoke 獲取運(yùn)行結(jié)果而移除了 GetPrice 方法的處理,,這正是開發(fā)人員最不想看到的,。 為了保證系統(tǒng)的封裝性,開發(fā)往往需要把委托對象 GetPriceHandler 設(shè)置為 private, 再分別加入 AddHandler,,RemoveHandler 方法對 GetPriceHandler 委托對象進(jìn)行封裝,。
1 public delegate double PriceHandler(); 2 3 public class PriceManager 4 { 5 private PriceHandler GetPriceHandler; 6 7 //委托處理,當(dāng)價格高于100元按8.8折計算,,其他按原價計算 8 public double GetPrice() 9 { 10 if (GetPriceHandler!=null) 11 { 12 if (GetPriceHandler() > 100) 13 return GetPriceHandler()*0.88; 14 else 15 return GetPriceHandler(); 16 } 17 return -1; 18 } 19 20 public void AddHandler(PriceHandler handler) 21 { 22 GetPriceHandler += handler; 23 } 24 25 public void RemoveHandler(PriceHandler handler) 26 { 27 GetPriceHandler -= handler; 28 } 29 } 30 ................ 31 ................
為了保存封裝性,,很多操作都需要加入AddHandler、RemoveHandler 這些相似的方法代碼,,這未免令人感到厭煩,。 為了進(jìn)一步簡化操作,,事件這個概念應(yīng)運(yùn)而生。
4.2 事件的定義
事件(event)可被視作為一種特別的委托,,它為委托對象隱式地建立起add_XXX,、remove_XXX
兩個方法,用作注冊與注銷事件的處理方法,。而且事件對應(yīng)的變量成員將會被視為 private
變量,,外界無法超越事件所在對象直接訪問它們,這使事件具備良好的封裝性,,而且免除了add_XXX,、remove_XXX等繁瑣的代碼。
1 public class EventTest 2 { 3 public delegate void MyDelegate(); 4 public event MyDelegate MyEvent; 5 }
觀察事件的編譯過程可知,,在編譯的時候,,系統(tǒng)為 MyEvent 事件自動建立add_MyEvent,、remove_MyEvent 方法,。
4.3 事件的使用方式
事件能通過+=和-=兩個方式注冊或者注銷對其處理的方法,使用+=與-=操作符的時候,,系統(tǒng)會自動調(diào)用對應(yīng)的 add_XXX,、remove_XXX 進(jìn)行處理。 值
得留意,,在PersonManager類的Execute方法中,,如果 MyEvent
綁定的處理方法不為空,即可使用MyEvent(string)引發(fā)事件,。但如果在外界的 main 方法中直接使用
personManager.MyEvent (string)
來引發(fā)事件,,系統(tǒng)將引發(fā)錯誤報告。這正是因為事件具備了良好的封裝性,,使外界不能超越事件所在的對象訪問其變量成員,。
注意:在事件所處的對象之外,事件只能出現(xiàn)在+=,,-=的左方,。
此時,開發(fā)人員無須手動添加 add_XXX,、remove_XXX 的方法,,就可實現(xiàn)與4.1例子中的相同功能,實現(xiàn)了良好的封裝,。
1 public delegate void MyDelegate(string name); 2 3 public class PersonManager 4 { 5 public event MyDelegate MyEvent; 6 7 //執(zhí)行事件 8 public void Execute(string name) 9 { 10 if (MyEvent != null) 11 MyEvent(name); 12 } 13 } 14 15 class Program 16 { 17 static void Main(string[] args) 18 { 19 PersonManager personManager = new PersonManager(); 20 //綁定事件處理方法 21 personManager.MyEvent += new MyDelegate(GetName); 22 personManager.Execute("Leslie"); 23 Console.ReadKey(); 24 } 25 26 public static void GetName(string name) 27 { 28 Console.WriteLine("My name is " + name); 29 } 30 }
4.4 事件處理方法的綁定
在綁定事件處理方法的時候,,事件出現(xiàn)在+=、-= 操作符的左邊,,對應(yīng)的委托對象出現(xiàn)在+=,、-= 操作符的右邊,。對應(yīng)以上例子,事件提供了更簡單的綁定方式,,只需要在+=,、-= 操作符的右方寫上方法名稱,系統(tǒng)就能自動辯認(rèn),。
1 public delegate void MyDelegate(string name); 2 3 public class PersonManager 4 { 5 public event MyDelegate MyEvent; 6 ......... 7 } 8 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 PersonManager personManager = new PersonManager(); 14 //綁定事件處理方法 15 personManager.MyEvent += GetName; 16 ............. 17 } 18 19 public static void GetName(string name) 20 {.........} 21 }
如果覺得編寫 GetName 方法過于麻煩,,你還可以使用匿名方法綁定事件的處理。
1 public delegate void MyDelegate(string name); 2 3 public class PersonManager 4 { 5 public event MyDelegate MyEvent; 6 7 //執(zhí)行事件 8 public void Execute(string name) 9 { 10 if (MyEvent != null) 11 MyEvent(name); 12 } 13 14 static void Main(string[] args) 15 { 16 PersonManager personManager = new PersonManager(); 17 //使用匿名方法綁定事件的處理 18 personManager.MyEvent += delegate(string name){ 19 Console.WriteLine("My name is "+name); 20 }; 21 personManager.Execute("Leslie"); 22 Console.ReadKey(); 23 } 24 }
4.5 C#控件中的事件
在C#控件中存在多個的事件,,像Click,、TextChanged、SelectIndexChanged 等等,,很多都是通過 EventHandler 委托綁定事件的處理方法的,,EventHandler 可說是C#控件中最常見的委托 。
public delegate void EventHandler (Object sender, EventArgs e)
EventHandler 委托并無返回值,,sender 代表引發(fā)事件的控件對象,,e 代表由該事件生成的數(shù)據(jù) 。在ASP.NET中可以直接通過btn.Click+=new EventHandler(btn_onclick) 的方式為控件綁定處理方法,。
1 <html xmlns="http://www./1999/xhtml"> 2 <head runat="server"> 3 <title></title> 4 <script type="text/C#" runat="server"> 5 protected void Page_Load(object sender, EventArgs e) 6 { 7 btn.Click += new EventHandler(btn_onclick); 8 } 9 10 public void btn_onclick(object obj, EventArgs e) 11 { 12 Button btn = (Button)obj; 13 Response.Write(btn.Text); 14 } 15 </script> 16 </head> 17 <body> 18 <form id="form1" runat="server"> 19 <div> 20 <asp:Button ID="btn" runat="server" Text="Button"/> 21 </div> 22 </form> 23 </body> 24 </html>
更多時候,,只需要在頁面使用 OnClick=“btn_onclick" 方法,在編譯的時候系統(tǒng)就會自動對事件處理方法進(jìn)行綁定,。
1 <html xmlns="http://www./1999/xhtml"> 2 <head runat="server"> 3 <title></title> 4 <script type="text/C#" runat="server"> 5 public void btn_onclick(object obj, EventArgs e) 6 { 7 Button btn = (Button)obj; 8 Response.Write(btn.Text); 9 } 10 </script> 11 </head> 12 <body> 13 <form id="form1" runat="server"> 14 <div> 15 <asp:Button ID="btn" runat="server" Text="Button" OnClick="btn_onclick"/> 16 </div> 17 </form> 18 </body> 19 </html>
EventHandler 只是 EventHandler<TEventArgs> 泛型委托的一個簡單例子,。事實上,大家可以利用 EventHandler<TEventArgs> 構(gòu)造出所需要的委托,。
public delegate void EventHandler<TEventArgs> (Object sender, TEventArgs e)
在EventHandler<TEventArgs>中,,sender代表事件源,e
代表派生自EventArgs類的事件參數(shù),。開發(fā)人員可以建立派生自EventArgs的類,,從中加入需要使用到的事件參數(shù),然后建立
EventHandler<TEventArgs> 委托,。
下面的例子中,,先建立一個派生自EventArgs的類MyEventArgs作為事件參數(shù),然后在EventManager中建立事件
myEvent , 通過 Execute 方法可以激發(fā)事件,。最后在測試中綁定 myEvent 的處理方法
ShowMessage,,在ShowMessage顯示myEventArgs 的事件參數(shù) Message。
1 public class MyEventArgs : EventArgs 2 { 3 private string args; 4 5 public MyEventArgs(string message) 6 { 7 args = message; 8 } 9 10 public string Message 11 { 12 get { return args; } 13 set { args = value; } 14 } 15 } 16 17 public class EventManager 18 { 19 public event EventHandler<MyEventArgs> myEvent; 20 21 public void Execute(string message) 22 { 23 if (myEvent != null) 24 myEvent(this, new MyEventArgs(message)); 25 } 26 } 27 28 class Program 29 { 30 static void Main(string[] args) 31 { 32 EventManager eventManager = new EventManager(); 33 eventManager.myEvent += new EventHandler<MyEventArgs>(ShowMessage); 34 eventManager.Execute("How are you!"); 35 Console.ReadKey(); 36 } 37 38 public static void ShowMessage(object obj,MyEventArgs e) 39 { 40 Console.WriteLine(e.Message); 41 } 42 }
運(yùn)行結(jié)果
4.6 為用戶控件建立事件
在ASP.NET開發(fā)中,,頁面往往會出現(xiàn)很多類似的控件與代碼,,開發(fā)人員可以通過用戶控件來避免重復(fù)的代碼。但往往同一個用戶控件,,在不同的頁面中需要有不同的響應(yīng),。此時為用戶控件建立事件,,便可輕松地解決此問題。 下
面例子中,,在用戶控件 MyControl 中建立存在一個GridView控件,,GridView 控件通過 GetPersonList
方法獲取數(shù)據(jù)源。在用戶控件中還定義了 RowCommand 事件,,在 GridView 的 GridView_RowCommand
方法中激發(fā)此事件,。這樣,在頁面使用此控件時,,開發(fā)人員就可以定義不同的方法處理 RowCommand 事件,。
1 public class Person 2 { 3 public int ID 4 { get; set; } 5 public string Name 6 { get; set; } 7 public int Age 8 { get; set; } 9 } 10 11 <!-- 用戶控件 --> 12 <%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyControl.ascx.cs" Inherits="MyControl" %> 13 <script type="text/C#" runat="server"> 14 protected void Page_Load(object sender, EventArgs e) 15 { 16 GridView1.DataSource = GetPersonList(); 17 GridView1.DataBind(); 18 } 19 20 //綁定數(shù)據(jù)源 21 protected IList<Person> GetPersonList() 22 { 23 IList<Person> list = new List<Person>(); 24 Person person1 = new Person(); 25 person1.ID = 1; 26 person1.Name = "Leslie"; 27 person1.Age = 29; 28 list.Add(person1); 29 ........... 30 return list; 31 } 32 33 public event GridViewCommandEventHandler RowCommand; 34 35 protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e) 36 { 37 if (RowCommand != null) 38 RowCommand(sender, e); 39 } 40 </script> 41 <div> 42 <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" 43 onrowcommand="GridView1_RowCommand"> 44 <Columns> 45 <asp:BoundField DataField="ID" HeaderText="ID"/> 46 <asp:BoundField DataField="Name" HeaderText="Name"/> 47 <asp:BoundField DataField="Age" HeaderText="Age"/> 48 <asp:ButtonField CommandName="Get" Text="Select"/> 49 </Columns> 50 </asp:GridView> 51 </div> 52 53 <!-- 頁面代碼 --> 54 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %> 55 <%@ Register Src="~/MyControl.ascx" TagPrefix="ascx" TagName="myControl" %> 56 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd"> 57 58 <html xmlns="http://www./1999/xhtml"> 59 <head runat="server"> 60 <title></title> 61 <script type="text/C#" runat="server"> 62 protected void myControl_RowCommand(object sender, GridViewCommandEventArgs e) 63 { 64 if (e.CommandName == "Get") 65 { 66 GridView gridView=(GridView)sender; 67 int index = int.Parse(e.CommandArgument.ToString()); 68 label.Text=gridView.Rows[index].Cells[1].Text; 69 } 70 } 71 </script> 72 </head> 73 <body> 74 <form id="form1" runat="server"> 75 <div> 76 <ascx:myControl ID="myControl" runat="server" OnRowCommand="myControl_RowCommand"></ascx:myControl> 77 <br /> 78 Select Name : <asp:Label ID="label" runat="server"></asp:Label><br /> 79 </div> 80 </form> 81 </body> 82 </html>
運(yùn)行結(jié)果
使用控件已有的事件固然簡單,但它限制了傳送的參數(shù)類型,,使開發(fā)人員無法傳送額外的自定義參數(shù),。在結(jié)構(gòu)比較復(fù)雜的用戶控件中,使用已有的控件事件,,顯然不夠方便,,此時,您可以考慮為用戶控件建立自定義事件,。 首
先用戶控件中包含訂單信息與訂單明細(xì)列表,,首先定義一個事件參數(shù) MyEventArgs,,里面包含了訂單信息與一個 OrderItem
數(shù)組,。然后建立用戶控件的委托MyDelegate 與對應(yīng)的事件 MyEvent,在 Button 的 Click 事件中激發(fā) MyEvent
自定義事件,。這樣在頁面處理方法 myControl_Click 中就可以通過事件參數(shù) MyEventArgs
獲取用戶控件中的屬性,,計算訂單的總體價格。
1 <!-- 基礎(chǔ)類 --> 2 public class OrderItem 3 { 4 public OrderItem(string id,string goods,double price,int count) 5 { 6 this.OrderItemID = id; //明細(xì)單ID 7 this.Goods = goods; //商品名稱 8 this.Price = price; //商品單價 9 this.Count = count; //商品數(shù)量 10 } 11 12 public string OrderItemID 13 { get; set; } 14 public string Goods 15 { get; set; } 16 public double Price 17 { get; set; } 18 public int Count 19 { get; set; } 20 } 21 22 /// 事件參數(shù) 23 public class MyEventArgs:EventArgs 24 { 25 public MyEventArgs(string name,string address,string tel, 26 string orderCode,IList<OrderItem> orderItemList) 27 { 28 Name = name; //買家姓名 29 Address = address; //買家地址 30 Tel = tel; //買家電話 31 OrderCode = orderCode; //訂單號碼 32 OrderItemList = orderItemList; //訂單明細(xì) 33 } 34 35 public string Name 36 { get;set; } 37 public string Address 38 { get; set; } 39 public string Tel 40 { get; set; } 41 public string OrderCode 42 { get; set; } 43 public IList<OrderItem> OrderItemList 44 { get; set; } 45 } 46 47 <!-- 用戶控件 --> 48 <%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyControl.ascx.cs" Inherits="MyControl" %> 49 <script type="text/C#" runat="server"> 50 protected void Page_Load(object sender, EventArgs e) 51 { 52 GridView1.DataSource = GetList(); 53 GridView1.DataBind(); 54 } 55 56 //模擬數(shù)據(jù)源 57 protected IList<OrderItem> GetList() 58 { 59 IList<OrderItem> list = new List<OrderItem>(); 60 OrderItem orderItem = new OrderItem("1", "Asus N75S", 8800, 2); 61 list.Add(orderItem); 62 .......... 63 return list; 64 } 65 66 //自定義委托 67 public delegate void MyDelegate(object sender,MyEventArgs myEventArgs); 68 //自定義事件 69 public event MyDelegate MyEvent; 70 71 //按下Button時激發(fā)自定義事件 72 protected void btn_click(object sender, EventArgs e) 73 { 74 if (MyEvent != null) 75 { 76 MyEventArgs myEventArgs = new MyEventArgs(labelName.Text, labelAddress.Text, labelTel.Text 77 , labelOrderCode.Text, GetList()); 78 MyEvent(this,myEventArgs); 79 } 80 } 81 </script> 82 <div> 83 Name : <asp:Label ID="labelName" runat="server">Leslie</asp:Label><br /> 84 Address : <asp:Label ID="labelAddress" runat="server">ZhongShan University 2A 501</asp:Label><br /> 85 Tel : <asp:Label ID="labelTel" runat="server">13660123456</asp:Label><br /> 86 Order Code : <asp:Label ID="labelOrderCode" runat="server">A12012031223B0030</asp:Label><br /><br /> 87 <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" CellPadding="5"> 88 <Columns> 89 <asp:BoundField DataField="OrderItemID" HeaderText="ID"/> 90 <asp:BoundField DataField="Goods" HeaderText="Goods"/> 91 <asp:BoundField DataField="Price" HeaderText="Price"/> 92 <asp:BoundField DataField="Count" HeaderText="Count"/> 93 </Columns> 94 </asp:GridView> 95 <br /> 96 <asp:Button ID="btn" runat="server" Text="Account" OnClick="btn_click"/> 97 </div> 98 99 <!-- 頁面處理 --> 100 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %> 101 <%@ Register Src="~/MyControl.ascx" TagPrefix="ascx" TagName="myControl" %> 102 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd"> 103 104 <html xmlns="http://www./1999/xhtml"> 105 <head runat="server"> 106 <title></title> 107 <script type="text/C#" runat="server"> 108 //在頁面定義用戶控件MyEvent事件的處理方法 109 protected void myControl_Click(object sender,MyEventArgs e) 110 { 111 //計算訂單總體價格 112 double totalPrice=0; 113 IList<OrderItem> list=e.OrderItemList; 114 foreach(OrderItem item in list) 115 totalPrice+=item.Price*item.Count; 116 //展示訂單號及總體費(fèi)用 117 labelOrderCode.Text = e.OrderCode; 118 labelTotalPrice.Text = totalPrice.ToString(); 119 } 120 </script> 121 </head> 122 <body> 123 <form id="form1" runat="server"> 124 <div> 125 <ascx:myControl ID="myControl" runat="server" OnMyEvent="myControl_Click"></ascx:myControl> 126 <br /> 127 OrderCode : <asp:Label ID="labelOrderCode" runat="server"></asp:Label><br /> 128 TotalPrice : <asp:Label ID="labelTotalPrice" runat="server"></asp:Label> 129 </div> 130 </form> 131 </body> 132 </html>
運(yùn)行結(jié)果
若對自定義事件不太熟悉的朋友很多時候會使用 UserControl.FindControl 的方式獲取用戶控件中的屬性,,但當(dāng)你深入了解自定義事件的開發(fā)過程以后,,就能有效簡化開發(fā)的過程。
回到目錄
五,、Lambda 表達(dá)式
5.1 Lambda 的意義
在Framework 2.0 以前,,聲明委托的唯一方法是通過方法命名,從Framework 2.0 起,,系統(tǒng)開始支持匿名方法,。 通過匿名方法,可以直接把一段代碼綁定給事件,,因此減少了實例化委托所需的編碼系統(tǒng)開銷,。 而在 Framework 3.0 開始,Lambda 表達(dá)式開始逐漸取代了匿名方法,,作為編寫內(nèi)聯(lián)代碼的首選方式,??傮w來說,Lambda 表達(dá)式的作用是為了使用更簡單的方式來編寫匿名方法,,徹底簡化委托的使用方式,。
5.2 回顧匿名方法的使用
匿名方法的使用已經(jīng)在4.4節(jié)簡單介紹過,在此回顧一下,。 使用下面的方式,,可以通過匿名方法為Button的Click事件綁定處理方法。
1 static void Main(string[] args) 2 { 3 Button btn = new Button(); 4 btn.Click+=delegate(object obj,EventArgs e){ 5 MessageBox.Show("Hello World !"); 6 }; 7 }
總是使用 delegate(){......} 的方式建立匿名方法,,令人不禁感覺郁悶,。于是從Framework 3.0 起, Lambda 表達(dá)式開始出現(xiàn),。
5.3 簡單介紹泛型委托
在介紹 Lambda 表達(dá)式前,,先介紹一下常用的幾個泛型委托。
5.3.1 泛型委托 Predicate<T>
早在Framework 2.0 的時候,,微軟就為 List<T> 類添加了 Find,、FindAll 、ForEach 等方法用作數(shù)據(jù)的查找,。
public T Find ( Predicate<T> match) public List<T> FindAll(Predicate<T> match)
在這些方法中存在一個Predicate <T> 表達(dá)式,,它是一個返回bool的泛型委托,能接受一個任意類型的對象作為參數(shù),。
public delegate bool Predicate<T>(T obj)
在下面例子中,,Predicate 委托綁定了參數(shù)為Person類的方法Match作為查詢條件,然后使用 FindAll 方法查找到合適條件的 List<Person> 集合,。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 List<Person> list = GetList(); 6 //綁定查詢條件 7 Predicate<Person> predicate = new Predicate<Person>(Match); 8 List<Person> result = list.FindAll(predicate); 9 Console.WriteLine(“Person count is : ” + result.Count); 10 Console.ReadKey(); 11 } 12 //模擬源數(shù)據(jù) 13 static List<Person> GetList() 14 { 15 var personList = new List<Person>(); 16 var person1 = new Person(1,"Leslie",29); 17 personList.Add(person1); 18 ........ 19 return personList; 20 } 21 //查詢條件 22 static bool Match(Person person) 23 { 24 return person.Age <= 30; 25 } 26 } 27 28 public class Person 29 { 30 public Person(int id, string name, int age) 31 { 32 ID = id; 33 Name = name; 34 Age = age; 35 } 36 37 public int ID 38 { get; set; } 39 public string Name 40 { get; set; } 41 public int Age 42 { get; set; } 43 }
5.3.2 泛型委托 Action
Action<T> 的使用方式與 Predicate<T> 相似,,不同之處在于 Predicate<T> 返回值為 bool , Action<T> 的返回值為 void。 Action 支持0~16個參數(shù),,可以按需求任意使用,。
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)
1 static void Main(string[] args) 2 { 3 Action<string> action=ShowMessage; 4 action("Hello World"); 5 Console.ReadKey(); 6 } 7 8 static void ShowMessage(string message) 9 { 10 MessageBox.Show(message); 11 }
5.3.3 泛型委托 Func
委托 Func 與 Action 相似,,同樣支持 0~16 個參數(shù),,不同之處在于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)
1 static void Main(string[] args) 2 { 3 Func<double, bool, double> func = Account; 4 double result=func(1000, true); 5 Console.WriteLine("Result is : "+result); 6 Console.ReadKey(); 7 } 8 9 static double Account(double a,bool condition) 10 { 11 if (condition) 12 return a * 1.5; 13 else 14 return a * 2; 15 }
5.4 揭開 Lambda 神秘的面紗
Lambda 的表達(dá)式的編寫格式如下:
x=> x * 1.5
當(dāng)中 “ => ” 是 Lambda 表達(dá)式的操作符,在左邊用作定義一個參數(shù)列表,,右邊可以操作這些參數(shù),。
例子一, 先把 int x 設(shè)置 1000,通過 Action 把表達(dá)式定義為 x=x+500 ,,最后通過 Invoke 激發(fā)委托,。
1 static void Main(string[] args) 2 { 3 int x = 1000; 4 Action action = () => x = x + 500; 5 action.Invoke(); 6 7 Console.WriteLine("Result is : " + x); 8 Console.ReadKey(); 9 }
例子二,通過 Action<int> 把表達(dá)式定義 x=x+500,, 到最后輸入?yún)?shù)1000,,得到的結(jié)果與例子一相同。 注意,,此處Lambda表達(dá)式定義的操作使用 { } 括弧包括在一起,,里面可以包含一系列的操作。
1 static void Main(string[] args) 2 { 3 Action<int> action = (x) => 4 { 5 x = x + 500; 6 Console.WriteLine("Result is : " + x); 7 }; 8 action.Invoke(1000); 9 Console.ReadKey(); 10 }
例子三,,定義一個Predicate<int>,,當(dāng)輸入值大約等于1000則返回 true , 否則返回
false。與5.3.1的例子相比,,Predicate<T>的綁定不需要顯式建立一個方法,,而是直接在Lambda表達(dá)式里完成,簡潔方
便了不少,。
1 static void Main(string[] args) 2 { 3 Predicate<int> predicate = (x) => 4 { 5 if (x >= 1000) 6 return true; 7 else 8 return false; 9 }; 10 bool result=predicate.Invoke(500); 11 Console.ReadKey(); 12 }
例子四,,在計算商品的價格時,當(dāng)商品重量超過30kg則打9折,,其他按原價處理,。此時可以使用Func<double,int,double>,,參數(shù)1為商品原價,,參數(shù)2為商品重量,最后返回值為 double 類型,。
1 static void Main(string[] args) 2 { 3 Func<double, int, double> func = (price, weight) => 4 { 5 if (weight >= 30) 6 return price * 0.9; 7 else 8 return price; 9 }; 10 double totalPrice = func(200.0, 40); 11 Console.ReadKey(); 12 }
例子五,,使用Lambda為Button定義Click事件的處理方法。與5.2的例子相比,,使用Lambda比使用匿名方法更加簡單,。
1 static void Main(string[] args) 2 { 3 Button btn = new Button(); 4 btn.Click += (obj, e) => 5 { 6 MessageBox.Show("Hello World!"); 7 }; 8 Console.ReadKey(); 9 }
例子六,此處使用5.3.1的例子,,在List<Person>的FindAll方法中直接使用Lambda表達(dá)式,。 相比之下,使用Lambda表達(dá)式,,不需要定義Predicate<T>對象,,也不需要顯式設(shè)定綁定方法,,簡化了不工序。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 List<Person> personList = GetList(); 6 7 //查找年齡少于30年的人 8 List<Person> result=personList.FindAll((person) => person.Age =< 30); 9 Console.WriteLine("Person count is : " + result.Count); 10 Console.ReadKey(); 11 } 12 13 //模擬源數(shù)據(jù) 14 static List<Person> GetList() 15 { 16 var personList = new List<Person>(); 17 var person1 = new Person(1,"Leslie",29); 18 personList.Add(person1); 19 ....... 20 return personList; 21 } 22 } 23 24 public class Person 25 { 26 public Person(int id, string name, int age) 27 { 28 ID = id; 29 Name = name; 30 Age = age; 31 } 32 33 public int ID 34 { get; set; } 35 public string Name 36 { get; set; } 37 public int Age 38 { get; set; } 39 }
當(dāng)在使用LINQ技術(shù)的時候,,到底都會彌漫著 Lambda 的身影,,此時更能體現(xiàn) Lambda 的長處。 但 LINQ 涉及到分部類,,分部方法,,IEnumerable<T>,迭代器等多方面的知識,,這些已經(jīng)超出本章的介紹范圍,。 通過這一節(jié)的介紹,希望能夠幫助大家更深入地了解 Lambda 的使用,。
回到目錄
本章小結(jié)
本章主要介紹了委托(Delegate)的使用,,委托對象是一個派生自 System.MultcastDelegate 的類,它能通過
Invoke 方式進(jìn)行同步調(diào)用,,也可以通過 BeginInvoke,,EndInvoke
方式實現(xiàn)異步調(diào)用。而事件(Event)屬于一種特殊的委托,,它與委托類型同步使用,,可以簡化的開發(fā)過程。 最后,,本文還介紹了匿名方法的使用方式,,以及 Lambda 表達(dá)式的由來。 對.NET開發(fā)有興趣的朋友請加入博客園討論小組“.NET高級編程”一起探討,!
|