C#综合揭秘——深入分析委托与事件(中)

来源:互联网 发布:霍尼韦尔口罩与3m 知乎 编辑:程序博客网 时间:2024/06/05 07:24

引言

本篇文章将会讲述事件的定义以及其使用方式,并以ASP.NET的用户控件为例子,介绍一下自定义事件的使用。

 

目录

一、委托类型的来由

二、建立委托类

三、委托使用方式

四、深入解析事件

五、Lambda 表达式

四、深入解析事件

4.1 事件的由来

在介绍事件之前大家可以先看看下面的例子, PriceManager 负责对商品价格进行处理,当委托对象 GetPriceHandler 的返回值大于100元,按8.8折计算,低于100元按原价计算。

 1     public delegate double PriceHandler(); 2  3     public class PriceManager 4     { 5         public PriceHandler GetPriceHandler; 6  7         //委托处理,当价格高于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 else15 return GetPriceHandler();16 }17 return -1;18 }19 }20 21 class Program22 {23 static void Main(string[] args)24 {25 PriceManager priceManager = new PriceManager();26 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()));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 //计算机价格为8800元
51 public static double ComputerPrice()52 {53 return 8800.0;54 }55 }

运行结果

观察运行的结果,如果把委托对象 GetPriceHandler 设置为 public ,外界可以直接调用 GetPriceHandler.Invoke 获取运行结果而移除了 GetPrice 方法的处理,这正是开发人员最不想看到的。
为了保证系统的封装性,开发往往需要把委托对象 GetPriceHandler 设置为 private, 再分别加入 AddHandler,RemoveHandler 方法对 GetPriceHandler 委托对象进行封装。

 1     public delegate double PriceHandler(); 2  3     public class PriceManager 4     { 5         private PriceHandler GetPriceHandler; 6  7         //委托处理,当价格高于100元按8.8折计算,其他按原价计算
8 public double GetPrice() 9 {10 if (GetPriceHandler!=null)11 {12 if (GetPriceHandler() > 100)13 return GetPriceHandler()*0.88;14 else15 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 这些相似的方法代码,这未免令人感到厌烦。
为了进一步简化操作,事件这个概念应运而生。
 

4.2 事件的定义

事件(event)可被视作为一种特别的委托,它为委托对象隐式地建立起add_XXX、remove_XXX 两个方法,用作注册与注销事件的处理方法。而且事件对应的变量成员将会被视为 private 变量,外界无法超越事件所在对象直接访问它们,这使事件具备良好的封装性,而且免除了add_XXX、remove_XXX等繁琐的代码。

1     public class EventTest2     {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); 2  3     public class PersonManager 4     { 5         public event MyDelegate MyEvent; 6  7         //执行事件
8 public void Execute(string name) 9 {10 if (MyEvent != null)11 MyEvent(name);12 }13 }14 15 class Program16 {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 事件处理方法的绑定

在绑定事件处理方法的时候,事件出现在+=、-= 操作符的左边,对应的委托对象出现在+=、-= 操作符的右边。对应以上例子,事件提供了更简单的绑定方式,只需要在+=、-= 操作符的右方写上方法名称,系统就能自动辩认。

 1     public delegate void MyDelegate(string name); 2  3     public class PersonManager 4     { 5         public event MyDelegate MyEvent; 6         ......... 7     } 8  9     class Program10     {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         //执行事件
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 代表引发事件的控件对象,e 代表由该事件生成的数据 。在ASP.NET中可以直接通过btn.Click+=new EventHandler(btn_ 的方式为控件绑定处理方法。

 1 <html xmlns="http://www.w3.org/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_ 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_ 方法,在编译的时候系统就会自动对事件处理方法进行绑定。

 1 <html xmlns="http://www.w3.org/1999/xhtml"> 2 <head runat="server"> 3     <title></title> 4     <script type="text/C#" runat="server">
5 public void btn_ 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> 构造出所需要的委托。

public delegate void EventHandler<TEventArgs> (Object sender, TEventArgs e)

在EventHandler<TEventArgs>中,sender代表事件源,e 代表派生自EventArgs类的事件参数。开发人员可以建立派生自EventArgs的类,从中加入需要使用到的事件参数,然后建立 EventHandler<TEventArgs> 委托。

下面的例子中,先建立一个派生自EventArgs的类MyEventArgs作为事件参数,然后在EventManager中建立事件myEvent , 通过 Execute 方法可以激发事件。最后在测试中绑定 myEvent 的处理方法 ShowMessage,在ShowMessage显示myEventArgs 的事件参数 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 Message11         {12             get { return args; }13             set { args = value; }14         }15     }16 17     public class EventManager18     {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 Program29     {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     }

运行结果

 

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; } 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 //绑定数据源
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.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">57 58 <html xmlns="http://www.w3.org/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>

运行结果

 

使用控件已有的事件固然简单,但它限制了传送的参数类型,使开发人员无法传送额外的自定义参数。在结构比较复杂的用户控件中,使用已有的控件事件,显然不够方便,此时,您可以考虑为用户控件建立自定义事件。
首先用户控件中包含订单信息与订单明细列表,首先定义一个事件参数 MyEventArgs,里面包含了订单信息与一个 OrderItem 数组。然后建立用户控件的委托MyDelegate 与对应的事件 MyEvent,在 Button 的 Click 事件中激发 MyEvent 自定义事件。这样在页面处理方法 myControl_Click 中就可以通过事件参数 MyEventArgs 获取用户控件中的属性,计算订单的总体价格。

  1 <!--   基础类    -->  2  public class OrderItem  3  {  4      public OrderItem(string id,string goods,double price,int count)  5      {  6          this.OrderItemID = id;     //明细单ID  7          this.Goods = goods;        //商品名称  8          this.Price = price;        //商品单价  9          this.Count = count;        //商品数量  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  /// 事件参数 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;     //订单明细 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 //模拟数据源
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时激发自定义事件
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.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">103 104 <html xmlns="http://www.w3.org/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 //展示订单号及总体费用
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>

运行结果


若对自定义事件不太熟悉的朋友很多时候会使用 UserControl.FindControl 的方式获取用户控件中的属性,但当你深入了解自定义事件的开发过程以后,就能有效简化开发的过程。

原创粉丝点击