引言

本篇文章将会讲述事件的定义以及其使用方式,并以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 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 //调用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 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 这些相似的方法代码,这未免令人感到厌烦。
为了进一步简化操作,事件这个概念应运而生。
 

4.2 事件的定义

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

1     public class EventTest
2     {
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 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 事件处理方法的绑定

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

 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         //执行事件
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 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     }

运行结果

 

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 的方式获取用户控件中的属性,但当你深入了解自定义事件的开发过程以后,就能有效简化开发的过程。


回到目录