天天看點

C#實戰揭秘:深入解析委托與事件的由來

本篇文章将為你介紹一下 Delegate 的使用方式,逐漸揭開 C# 當中事件(Event)的由來,它能使處理委托類型的過程變得更加簡單。

  還将為您解釋委托的協變與逆變,以及如何使用 Delegate 使 Observer(觀察者)模式的使用變得更加簡單。

  在事件的介紹上,會講述事件的使用方式,并以ASP.NET的使用者控件為例子,介紹一下自定義事件的使用。

  最後一節,将介紹Predicate、Action、Func多種泛型委托的使用和Lambda的發展過程與其使用方式。

  因為時間倉促,文中有錯誤的地方敬請點評。

  目錄

  一、委托類型的來由

  二、建立委托類

  三、委托使用方式

  四、深入解析事件

  五、Lambda 表達式

  記得在使用C語言的年代,整個項目中都充滿着針指的身影,那時候流行使用函數指針來建立回調函數,使用回調可以把函數回調給程式中的另一個函數。但函數指針隻是簡單地把位址指向另一個函數,并不能傳遞其他額外資訊。

  在.NET中,在大部分時間裡都沒有指針的身影,因為指針被封閉在内部函數當中。可是回調函數卻依然存在,它是以委托的方式來完成的。委托可以被視為一個更進階的指針,它不僅僅能把位址指向另一個函數,而且還能傳遞參數,傳回值等多個資訊。系統還為委托對象自動生成了同步、異步的調用方式,開發人員使用 BeginInvoke、EndInvoke 方法就可以抛開 Thread 而直接使用多線程調用 。

  回到目錄

  使用delegate就可以直接建立任何名稱的委托類型,當進行系統編譯時,系統就會自動生成此類型。您可以使用delegate void MyDelegate() 方式建立一個委托類,并使用ILDASM.exe觀察其成員。由ILDASM.exe 中可以看到,它繼承了System.MulticastDelegate類,并自動生成BeginInvoke、EndInvoke、Invoke 等三個常用方法。

  Invoke 方法是用于同步調用委托對象的對應方法,而BeginInvoke、EndInvoke是用于以異步方式調用對應方法的。

  對于異步調用的使用方式,可以參考:C#綜合揭秘——細說多線程

1 public class MyDelegate:MulticastDelegate

  2 {

  3 //同步調用委托方法

  4 public virtual void Invoke();

  5 //異步調用委托方法

  6 public virtual IAsyncResult BeginInvoke(AsyncCallback callback,object state);

  7 public virtual void EndInvoke(IAsyncResult result);

  8 }

MulticastDelegate是System.Delegate的子類,它是一個特殊類,編譯器和其他工具可以從此類派生,但是自定義類不能顯式地從此類進行派生。它支援多路廣播委托,并擁有一個帶有連結的委托清單,在調用多路廣播委托時,系統将按照調用清單中的委托出現順序來同步調用這些委托。

  MulticastDelegate具有兩個常用屬性:Method、Target。其中Method 用于擷取委托所表示的方法Target 用于擷取目前調用的類執行個體。

  MulticastDelegate有以下幾個常用方法:

  方法名稱 說明

  Clone 建立委托的淺表副本。

  GetInvocationList 按照調用順序傳回此多路廣播委托的調用清單。

  GetMethodImpl 傳回由目前的 MulticastDelegate 表示的靜态方法。

  GetObjectData 用序列化該執行個體所需的所有資料填充 SerializationInfo 對象。

  MemberwiseClone 建立目前 Object 的淺表副本。

  RemoveImpl 調用清單中移除與指定委托相等的元素

  MulticastDelegate與Delegate給委托對象建立了強大的支援,下面向各位詳細介紹一下委托的使用方式。

  3.1 簡單的委托

  當建立委托對象時,委托的參數類型必須與委托方法相對應。隻要向建立委托對象的構造函數中輸入方法名稱example.Method,委托就會直接綁定此方法。使用myDelegate.Invoke(string message),就能顯式調用委托方法。但在實際的操作中,我們無須用到 Invoke 方法,而隻要直接使用myDelegate(string message),就能調用委托方法。

1 class Program

  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 帶傳回值的委托

  當建立委托對象時,委托的傳回值必須與委托方法相對應。使用下面的例子,方法将傳回 “Hello Leslie” 。

  3 delegate string MyDelegate(string message);

  7 public string Method(string name)

  9 return "Hello " + name;

  16 //綁定委托方法

  17 MyDelegate myDelegate=new MyDelegate(example.Method);

  18 //調用委托,擷取傳回值

  19 string message = myDelegate("Leslie");

  20 Console.WriteLine(message);

  21 Console.ReadKey();

  22 }

  23 }

  3.3 多路廣播委托

  在第二節前曾經提過,委托類繼承于MulticastDelegate,這使委托對象支援多路廣播,即委托對象可以綁定多個方法。當輸入參數後,每個方法會按順序進行疊代處理,并傳回最後一個方法的計算結果。

  下面的例子中,Price 類中有兩個計算方法,Ordinary 按普通的9.5折計算,Favourable 按優惠價 8.5 折計算。委托同時綁定了這兩個方法,在輸入參數100以後,Ordinary、Favourable這兩個方法将按順序疊代執行下去,最後傳回 Favourable 方法的計算結果 85。

1 delegate double MyDelegate(double message);

  2

  3 public class Price

  4 {

  5 public double Ordinary(double price)

  7 double price1 = 0.95 * price;

  8 Console.WriteLine("Ordinary Price : "+price1);

  9 return price1;

  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 //調用委托

  27 Console.WriteLine("Current Price : " + myDelegate(100));

  28 Console.ReadKey();

  29 }

  30 }

  運作結果

  3.4 淺談Observer模式

  回顧一下簡單的 Observer 模式,它使用一對多的方式,可以讓多個觀察者同時關注同一個事物,并作出不同的響應。

  例如下面的例子,Manager的底薪為基本工資的1.5倍,Assistant的底薪為基本工資的1.2倍。WageManager類的RegisterWorker方法與RemoveWorker方法可以用于注冊和登出觀察者,最後執行Execute方法可以對多個已注冊的觀察者同時輸入參數。

1 public class WageManager

  3 IList workerList = new List();

  5 public void RegisterWorker(Worker worker)

  7 workerList.Add(worker);

  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);

  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元,分别進行計算

  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 }

運作結果

  開發 Observer 模式時借助委托,可以進一步簡化開發的過程。由于委托對象支援多路廣播,是以可以把Worker類省略。在WageManager類中建立了一個委托對象wageHandler,通過Attach與Detach方法可以分别加入或取消委托。如果觀察者想對事物進行監測,隻需要加入一個委托對象即可。記得在第二節曾經提過,委托的GetInvodationList方法能擷取多路廣播委托清單,在Execute方法中,就是通過去多路廣播委托清單去判斷所綁定的委托數量是否為0。

1 public delegate double Handler(double basicWages);

  3 public class Manager

  5 public double GetWages(double basicWages)

  7 double totalWages=1.5 * basicWages;

  8 Console.WriteLine("Manager's wages is : " + totalWages);

  9 return totalWages;

  13 public class Assistant

  15 public double GetWages(double basicWages)

  17 double totalWages = 1.2 * basicWages;

  18 Console.WriteLine("Assistant's wages is : " + totalWages);

  19 return totalWages;

  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方法擷取多路廣播委托清單,如果觀察者數量大于0即執行方法

  40 public void Execute(double basicWages)

  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元,分别進行計算

  61 wageManager.Execute(3000);

  62 Console.ReadKey();

  63 }

  64 }

  最後運作結果與上面的例子相同。

  3.5 委托的協變與逆變

  在 Framework 2.0 出現之前,委托協變這個概念還沒有出現。此時因為委托是安全類型,它們不遵守繼承的基礎規則。即會這下面的情況:Manager 雖然是 Worker 的子類,但 GetWorkerHander 委托不能直接綁定 GetManager 方法,因為在委托當中它們的傳回值 Manager 與 Worker 被視為完全無關的兩個類型

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;

  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();

  34 }

  自從Framework 2.0 面試以後,委托協變的概念就應運而生,此時委托可以按照傳統的繼承規則進行轉換。即 GetWorkerHandler 委托可以直接綁定 GetManager 方法.

 1 public class Worker

  9 //在 Framework2.0 以上,委托 GetWorkerHandler 可綁定 GetWorker 與 GetManager 兩個方法

  14 return worker;

  15 }

  16

  17 public static Manager GetManager(int id)

  18 {

  19 Manager manager = new Manager();

  20 return manager;

  23 static void Main(string[] args)

  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();

  委托逆變,是指委托方法的參數同樣可以接收 “繼承” 這個傳統規則。像下面的例子,以 object 為參數的委托,可以接受任何 object 子類的對象作為參數。最後可以在處理方法中使用 is 對輸入資料的類型進行判斷,分别處理對不同的類型的對象。

  

 1 class Program

  3 public delegate void Handler(object obj);

  5 public static void GetMessage(object message)

  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());

  15 Handler handler = new Handler(GetMessage);

  16 handler(29);

  17 Console.ReadKey();

  18 }

  注意:委托與其綁定方法的參數必須一至,即當 Handler 所輸入的參數為 object 類型,其綁定方法 GetMessage 的參數也必須為 object 。否則,即使綁定方法的參數為 object 的子類,系統也無法辨認。

  3.6 泛型委托

  委托逆變雖然實用,但如果都以 object 作為參數,則需要每次都對參數進行類型的判斷,這不禁令人感到厭煩。

  為此,泛型委托應運而生,泛型委托有着委托逆變的優點,同時利用泛型的特性,可以使一個委托綁定多個不同類型參數的方法,而且在方法中不需要使用 is 進行類型判斷,進而簡化了代碼。

  3 public delegate void Handler(T obj);

  5 public static void GetWorkerWages(Worker worker)

  7 Console.WriteLine("Worker's total wages is " + worker.Wages);

  10 public static void GetManagerWages(Manager manager)

  12 Console.WriteLine("Manager's total wages is "+manager.Wages);

  15 static void Main(string[] args)

  17 Handler workerHander = new Handler(GetWorkerWages);

  18 Worker worker = new Worker();

  19 worker.Wages = 3000;

  20 workerHander(worker);

  22 Handler managerHandler = new Handler(GetManagerWages);

  23 Manager manager = new Manager();

  24 manager.Wages = 4500;

  25 managerHandler(manager);

  27 Console.ReadKey();

  28 }

  4.1 事件的由來

  在介紹事件之前大家可以先看看下面的例子, PriceManager 負責對商品價格進行處理,當委托對象 GetPriceHandler 的傳回值大于100元,按8.8折計算,低于100元按原價計算。

1 public delegate double PriceHandler();

2

  3 public class PriceManager

  5 public PriceHandler GetPriceHandler;

  6

  7 //委托處理,當價格高于100元按8.8折計算,其他按原價計算

  8 public double GetPrice()

  9 {

  10 if (GetPriceHandler.GetInvocationList().Count() > 0)

  12 if (GetPriceHandler() > 100)

  13 return GetPriceHandler()*0.88;

  14 else

  15 return GetPriceHandler();

  17 return -1;

  20

  21 class Program

  22 {

  25 PriceManager priceManager = new PriceManager();

  27 //調用priceManager的GetPrice方法擷取價格

  28 //直接調用委托的Invoke擷取價格,兩者進行比較

  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()));

  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;

  50 //計算機價格為8800元

  51 public static double ComputerPrice()

  53 return 8800.0;

  54 }

  55 }

  觀察運作的結果,如果把委托對象 GetPriceHandler 設定為 public ,外界可以直接調用 GetPriceHandler.Invoke 擷取運作結果而移除了 GetPrice 方法的處理,這正是開發人員最不想看到的。

  為了保證系統的封裝性,開發往往需要把委托對象 GetPriceHandler 設定為 private, 再分别加入 AddHandler,RemoveHandler 方法對 GetPriceHandler 委托對象進行封裝。

  5 private PriceHandler GetPriceHandler;

  10 if (GetPriceHandler!=null)

  19

  20 public void AddHandler(PriceHandler handler)

  21 {

  22 GetPriceHandler += handler;

  25 public void RemoveHandler(PriceHandler handler)

  27 GetPriceHandler -= handler;

  30 ................

  31 ................

  為了儲存封裝性,很多操作都需要加入AddHandler、RemoveHandler 這些相似的方法代碼,這未免令人感到厭煩。

  為了進一步簡化操作,事件這個概念應運而生。

  4.2 事件的定義

  事件(event)可被視作為一種特别的委托,它為委托對象隐式地建立起add_XXX、remove_XXX 兩個方法,用作注冊與登出事件的處理方法。而且事件對應的變量成員将會被視為 private 變量,外界無法超越事件所在對象直接通路它們,這使事件具備良好的封裝性,而且免除了add_XXX、remove_XXX等繁瑣的代碼。

  1 public class EventTest

  3 public delegate void MyDelegate();

  4 public event MyDelegate MyEvent;

  5 }

  觀察事件的編譯過程可知,在編譯的時候,系統為 MyEvent 事件自動建立add_MyEvent、remove_MyEvent 方法。

  4.3 事件的使用方式

  事件能通過+=和-=兩個方式注冊或者登出對其處理的方法,使用+=與-=操作符的時候,系統會自動調用對應的 add_XXX、remove_XXX 進行處理。

  值得留意,在PersonManager類的Execute方法中,如果 MyEvent 綁定的處理方法不為空,即可使用MyEvent(string)引發事件。但如果在外界的 main 方法中直接使用 personManager.MyEvent (string) 來引發事件,系統将引發錯誤報告。這正是因為事件具備了良好的封裝性,使外界不能超越事件所在的對象通路其變量成員。

  注意:在事件所處的對象之外,事件隻能出現在+=,-=的左方。

  此時,開發人員無須手動添加 add_XXX、remove_XXX 的方法,就可實作與4.1例子中的相同功能,實作了良好的封裝。

 

 1 public delegate void MyDelegate(string name);

  3 public class PersonManager

  5 public event MyDelegate MyEvent;

  7 //執行事件

  8 public void Execute(string name)

  10 if (MyEvent != null)

  11 MyEvent(name);

  12 }

  15 class Program

  17 static void Main(string[] args)

  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);

  4.4 事件處理方法的綁定

  在綁定事件處理方法的時候,事件出現在+=、-= 操作符的左邊,對應的委托對象出現在+=、-= 操作符的右邊。對應以上例子,事件提供了更簡單的綁定方式,隻需要在+=、-= 操作符的右方寫上方法名稱,系統就能自動辯認。

1 public delegate void MyDelegate(string name);

  6 .........

  7 }

  8

  9 class Program

  10 {

  11 static void Main(string[] args)

  13 PersonManager personManager = new PersonManager();

  14 //綁定事件處理方法

  15 personManager.MyEvent += GetName;

  16 .............

  19 public static void GetName(string name)

  20 {.........}

  如果覺得編寫 GetName 方法過于麻煩,你還可以使用匿名方法綁定事件的處理。

  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();

 4.5 C#控件中的事件

  在C#控件中存在多個的事件,像Click、TextChanged、SelectIndexChanged 等等,很多都是通過 EventHandler 委托綁定事件的處理方法的,EventHandler 可說是C#控件中最常見的委托 。

  public delegate void EventHandler (Object sender, EventArgs e)

  EventHandler 委托并無傳回值,sender 代表引發事件的控件對象,e 代表由該事件生成的資料 。在ASP.NET中可以直接通過btn.Click+=new EventHandler(btn_ 的方式為控件綁定處理方法。

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 }

10 public void btn_ 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_ 方法,在編譯的時候系統就會自動對事件處理方法進行綁定。

5 public void btn_ obj, EventArgs e)

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 泛型委托的一個簡單例子。事實上,大家可以利用 EventHandler 構造出所需要的委托。

  public delegate void EventHandler (Object sender, TEventArgs e)

  在EventHandler中,sender代表事件源,e 代表派生自EventArgs類的事件參數。開發人員可以建立派生自EventArgs的類,從中加入需要使用到的事件參數,然後建立 EventHandler 委托。

  下面的例子中,先建立一個派生自EventArgs的類MyEventArgs作為事件參數,然後在EventManager中建立事件myEvent , 通過 Execute 方法可以激發事件。最後在測試中綁定 myEvent 的處理方法 ShowMessage,在ShowMessage顯示myEventArgs 的事件參數 Message。

1 public class MyEventArgs : EventArgs

  3 private string args;

  5 public MyEventArgs(string message)

  7 args = message;

  10 public string Message

  12 get { return args; }

  13 set { args = value; }

  14 }

  17 public class EventManager

  19 public event EventHandler myEvent;

  21 public void Execute(string message)

  23 if (myEvent != null)

  24 myEvent(this, new MyEventArgs(message));

  25 }

  26 }

  27

  28 class Program

  30 static void Main(string[] args)

  31 {

  32 EventManager eventManager = new EventManager();

  33 eventManager.myEvent += new EventHandler(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 }

  4.6 為使用者控件建立事件

  在ASP.NET開發中,頁面往往會出現很多類似的控件與代碼,開發人員可以通過使用者控件來避免重複的代碼。但往往同一個使用者控件,在不同的頁面中需要有不同的響應。此時為使用者控件建立事件,便可輕松地解決此問題。

  下面例子中,在使用者控件 MyControl 中建立存在一個GridView控件,GridView 控件通過 GetPersonList 方法擷取資料源。在使用者控件中還定義了 RowCommand 事件,在 GridView 的 GridView_RowCommand 方法中激發此事件。這樣,在頁面使用此控件時,開發人員就可以定義不同的方法處理 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; }

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 //綁定資料源

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" %>

57 

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>

   運作結果

  使用控件已有的事件固然簡單,但它限制了傳送的參數類型,使開發人員無法傳送額外的自定義參數。在結構比較複雜的使用者控件中,使用已有的控件事件,顯然不夠友善,此時,您可以考慮為使用者控件建立自定義事件。

  首先使用者控件中包含訂單資訊與訂單明細清單,首先定義一個事件參數 MyEventArgs,裡面包含了訂單資訊與一個 OrderItem 數組。然後建立使用者控件的委托MyDelegate 與對應的事件 MyEvent,在 Button 的 Click 事件中激發 MyEvent 自定義事件。這樣在頁面處理方法 myControl_Click 中就可以通過事件參數 MyEventArgs 擷取使用者控件中的屬性,計算訂單的總體價格。

   

  若對自定義事件不太熟悉的朋友很多時候會使用 UserControl.FindControl 的方式擷取使用者控件中的屬性,但當你深入了解自定義事件的開發過程以後,就能有效簡化開發的過程。

  5.1 Lambda 的意義

  在Framework 2.0 以前,聲明委托的唯一方法是通過方法命名,從Framework 2.0 起,系統開始支援匿名方法。

  通過匿名方法,可以直接把一段代碼綁定給事件,是以減少了執行個體化委托所需的編碼系統開銷。

  而在 Framework 3.0 開始,Lambda 表達式開始逐漸取代了匿名方法,作為編寫内聯代碼的首選方式。總體來說,Lambda 表達式的作用是為了使用更簡單的方式來編寫匿名方法,徹底簡化委托的使用方式。

  5.2 回顧匿名方法的使用

  匿名方法的使用已經在4.4節簡單介紹過,在此回顧一下。

  使用下面的方式,可以通過匿名方法為Button的Click事件綁定處理方法。

  1 static void Main(string[] args)

  3 Button btn = new Button();

  4 btn.Click+=delegate(object obj,EventArgs e){

  5 MessageBox.Show("Hello World !");

  6 };

  複制代碼

  總是使用 delegate(){......} 的方式建立匿名方法,令人不禁感覺郁悶。于是從Framework 3.0 起, Lambda 表達式開始出現。

  5.3 簡單介紹泛型委托

  在介紹 Lambda 表達式前,先介紹一下常用的幾個泛型委托。

  5.3.1 泛型委托 Predicate

  早在Framework 2.0 的時候,微軟就為 List 類添加了 Find、FindAll 、ForEach 等方法用作資料的查找。

  public T Find ( Predicate match)

  public List FindAll(Predicate match)

  在這些方法中存在一個Predicate 表達式,它是一個傳回bool的泛型委托,能接受一個任意類型的對象作為參數。

  public delegate bool Predicate(T obj)

  在下面例子中,Predicate 委托綁定了參數為Person類的方法Match作為查詢條件,然後使用 FindAll 方法查找到合适條件的 List 集合。

  3 static void Main(string[] args)

  5 List list = GetList();

  6 //綁定查詢條件

  7 Predicate predicate = new Predicate(Match);

  8 List result = list.FindAll(predicate);

  9 Console.WriteLine(“Person count is : ” + result.Count);

  10 Console.ReadKey();

  12 //模拟源資料

  13 static List GetList()

  15 var personList = new List();

  16 var person1 = new Person(1,"Leslie",29);

  17 personList.Add(person1);

  18 ........

  19 return personList;

  21 //查詢條件

  22 static bool Match(Person person)

  24 return person.Age <= 30;

  28 public class Person

  30 public Person(int id, string name, int age)

  32 ID = id;

  33 Name = name;

  34 Age = age;

  35 }

  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 的使用方式與 Predicate 相似,不同之處在于 Predicate 傳回值為 bool , Action 的傳回值為 void。

  Action 支援0~16個參數,可以按需求任意使用。

 public delegate void Action()

  public delegate void Action(T1 obj1)

  public delegate void Action (T1 obj1, T2 obj2)

  public delegate void Action (T1 obj1, T2 obj2,T3 obj3)

  ............

  public delegate void Action (T1 obj1, T2 obj2,T3 obj3,......,T16 obj16)

  3 Action action=ShowMessage;

  4 action("Hello World");

  5 Console.ReadKey();

  6 }

  7

  8 static void ShowMessage(string message)

  10 MessageBox.Show(message);

  5.3.3 泛型委托 Func

  委托 Func 與 Action 相似,同樣支援 0~16 個參數,不同之處在于Func 必須具有傳回值

 public delegate TResult Func()

  public delegate TResult Func(T1 obj1)

  public delegate TResult Func(T1 obj1,T2 obj2)

  public delegate TResult Func(T1 obj1,T2 obj2,T3 obj3)

  public delegate TResult Func(T1 obj1,T2 obj2,T3 obj3,......,T16 obj16)

  3 Func func = Account;

  4 double result=func(1000, true);

  5 Console.WriteLine("Result is : "+result);

  6 Console.ReadKey();

  9 static double Account(double a,bool condition)

  11 if (condition)

  12 return a * 1.5;

  13 else

  14 return a * 2;

  5.4 揭開 Lambda 神秘的面紗

  Lambda 的表達式的編寫格式如下:

  x=> x * 1.5

  當中 “ => ” 是 Lambda 表達式的操作符,在左邊用作定義一個參數清單,右邊可以操作這些參數。

  例子一, 先把 int x 設定 1000,通過 Action 把表達式定義為 x=x+500 ,最後通過 Invoke 激發委托。

  3 int x = 1000;

  4 Action action = () => x = x + 500;

  5 action.Invoke();

  7 Console.WriteLine("Result is : " + x);

  8 Console.ReadKey();

  9 }

  例子二,通過 Action 把表達式定義 x=x+500, 到最後輸入參數1000,得到的結果與例子一相同。

  注意,此處Lambda表達式定義的操作使用 { } 括弧包括在一起,裡面可以包含一系列的操作。

  3 Action action = (x) =>

  5 x = x + 500;

  6 Console.WriteLine("Result is : " + x);

  7 };

  8 action.Invoke(1000);

  9 Console.ReadKey();

  例子三,定義一個Predicate,當輸入值大約等于1000則傳回 true , 否則傳回 false。與5.3.1的例子相比,Predicate的綁定不需要顯式建立一個方法,而是直接在Lambda表達式裡完成,簡潔友善了不少。

 1 static void Main(string[] args)

  3 Predicate predicate = (x) =>

  5 if (x >= 1000)

  6 return true;

  7 else

  8 return false;

  9 };

  10 bool result=predicate.Invoke(500);

  11 Console.ReadKey();

  例子四,在計算商品的價格時,當商品重量超過30kg則打9折,其他按原價處理。此時可以使用Func,參數1為商品原價,參數2為商品重量,最後傳回值為 double 類型。 

  3 Func func = (price, weight) =>

  5 if (weight >= 30)

  6 return price * 0.9;

  8 return price;

  10 double totalPrice = func(200.0, 40);

  例子五,使用Lambda為Button定義Click事件的處理方法。與5.2的例子相比,使用Lambda比使用匿名方法更加簡單。

  4 btn.Click += (obj, e) =>

  5 {

  6 MessageBox.Show("Hello World!");

  例子六,此處使用5.3.1的例子,在List的FindAll方法中直接使用Lambda表達式。

  相比之下,使用Lambda表達式,不需要定義Predicate對象,也不需要顯式設定綁定方法,簡化了不工序。

  5 List personList = GetList();

  7 //查找年齡少于30年的人

  8 List result=personList.FindAll((person) => person.Age =< 30);

  9 Console.WriteLine("Person count is : " + result.Count);

  13 //模拟源資料

  14 static List GetList()

  16 var personList = new List();

  17 var person1 = new Person(1,"Leslie",29);

  18 personList.Add(person1);

  19 .......

  20 return personList;

  23

  24 public class Person

  25 {

  26 public Person(int id, string name, int age)

  28 ID = id;

  29 Name = name;

  30 Age = age;

  33 public int ID

  34 { get; set; }

  35 public string Name

  36 { get; set; }

  37 public int Age

  39 }

  當在使用LINQ技術的時候,到底都會彌漫着 Lambda 的身影,此時更能展現 Lambda 的長處。

  但 LINQ 涉及到分部類,分部方法,IEnumerable,疊代器等多方面的知識,這些已經超出本章的介紹範圍。

  本章主要介紹了委托(Delegate)的使用,委托對象是一個派生自 System.MultcastDelegate 的類,它能通過 Invoke 方式進行同步調用,也可以通過 BeginInvoke,EndInvoke 方式實作異步調用。而事件(Event)屬于一種特殊的委托,它與委托類型同步使用,可以簡化的開發過程。

本文轉自 wws5201985 51CTO部落格,原文連結:http://blog.51cto.com/wws5201985/814885,如需轉載請自行聯系原作者

繼續閱讀