文章目的:作者(初学者)在学习c#的过程中,对事件、委托及其中的“object sender,EventArgs e”一直感觉理解不透,因此在网上找了一些资料,学习并整理出了该篇笔记,希望能将自己的心得记录下来作为积累。限于能力且是初学,有错误的地方还请大家批评指正。
注意:该笔记中有部分内容摘自网上的参考资料(如《浅析c#中的object sender和EventAgs e》),并非作者原创,仅限学习交流,特此声明!
一. 委托与事件
委托与事件是一对相互关联的概念。委托是一种引用类型,可通过声明委托变量,并将其初始化为某个匹配的函数来实现对该函数的引用。事件可以使类或对象向其他类或对象通知发生的相关事情,以使其他类或对象为此做出响应。灵活的事件处理要求将事件的响应分派给相应的事件处理程序(或称事件处理函数、事件处理方法),而C#中事件处理程序通常都是委托实现的。
二. 委托
1. 定义
委托其实是一种引用类型,它通过声明一个委托类型的变量,并通过将该变量初始化一个与该委托具有相同返回值类型和形参列表的函数引用,来实现所谓的对函数的引 用。
2. 声明方式
modifiers(opt) delegate return-Type identifier (fomal-parameter-list(opt))
修饰符(可选) 委托关键字 返回值类型 标识符 (形参列表(可选))
Public delegate double DataProcess (double x, double y)
3. 使用步骤
1)声明委托,指定其返回值类型和形参列表;
2)定义与委托具有相同返回值类型和形参列表的函数;
3) 声明委托类型的变量,并将该委托变量初始化为步骤2)中所定义的与该委托具有相同返回值类型和形参列表的函数引用.
4. 使用举例
1 public delegate double DataProcess(double x, double y);//声明委托DataProcess,指定其返回值类型和形参列表 2 class A 3 { 4 public static double Multiply(double x, double y); //定义与委托具有相同返回值类型和形参列表的函数Multiply() 5 { 6 return x*y; 7 } 8 DataProcess dataProcess = new DataProcess(Multiply);//定义了委托之后,声明该委托类型的变量dataProcess,接着把这个变量初始化为与委托具有相同返回值类型和 //形参列表的函数引用Multiply 9 }
三. 事件
1. 定义
1)发起者:引发事件的类或对象;
2) 订户:处理(或称“响应”)事件的类或对象;
3) 事件处理程序:订户提供的一些代码,用于在事件被引发时执行(事件处理程序可位于发起者所在的类,也可位于其他类);
4) 订阅:在发起者引发事件后,执行订户的事件处理程序(即将事件处理程序绑定到事件);
5) 事件:事件由发起者引发,由订户订阅,事件被引发后开始执行订阅该事件的由订户所提供的事件处理程序;
2. 声明方式
modifiers(opt) event Type identifier
修饰符(可选) 委托关键字 类型 标识符
Public event EventHanlder Click
四。 事件的分类及其使用步骤
这里将事件分为两种:1.NetFrameWork类库中已经为我们封装好的事件;2.我们自定义的事件。下面分别说明这两种事件的使用步骤,并给出代码示例:
1.NetFrameWork类库中封装好的事件的使用步骤
注意:在.NET Framwork类库中,事件是基于EventHandler(委托祖类)和EventArgs(事件数据祖类)两个基类的。
1)定义委托
委托的定义用于指定订阅该事件的事件处理程序(或事件处理方法、事件处理函数)的返回值类型及形参列表(此处以backgroundWorker组件为例);
public delegate void DoWorkEventHandler ( object sender, DoWorkEventArgs e);
a. 返回值类型:void
b. 参数1:object sender:
是一个对象,其实是一个对象引用,这里的事件是对象backgroundWorker1的事件,这个对象指的就是backgroundworker1。(backgroundWorker1是B ackgroundWorker类的一个实例)。
c. 参数2:DoWorkEventArgs e:
是一个包含事件数据的对象,用于传递事件的细节,DoWorkEventArgs是一个包含事件数据的类,它继承自包含事件数据的基类EventArgs;
注意:委托的返回值类型和形参列表根据自己的需要定义(包括命名),只要事件处理方法的返回值和参数与之匹配即可。此外委托并不需要与事件位于同一个类或 命名空间中,它可以位于其他类或命名空间,在.Net Framwork的类库中就是这样,自定义事件可根据需要安排。
2)定义事件
1 public event DoWorkEventHandler DoWork;
该事件位于BackgroundWorker类中,所以调用时为this.backgroundWorker1.DoWork,该事件在RunWorkerAsync()函数中引发,该函数也位于 BackgroundWorker类中。
3)定义监听事件函数(监听事件函数用于在满足一定条件时引发事件)
事件DoWork的监听事件函数是RunWorkerAsync(),该函数已经由.NET在BackgroundWorker类中定义,我们直接用就可以。
4)定义事件处理函数
private void Process(object sender, DoWorkEventArgs e) { Console.writeln(“the event has processed!”); }
注意:要求事件处理函数与委托具有匹配的返回值类型和参数列表。
5)为事件添加事件处理函数
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(Process);
以上便是.net类库封装的事件的使用过程,但是事件处理函数的添加一般在组件(或控件)的属性中设置即可,另外委托、事件和监听事件函数.NET Framework类 库已经为我们定义好,直接用就可以,我们唯一需要做的就是编写事件处理函数,我们可以在事件处理函数中写入事件发生时我们想做的事情。
2.自定义事件使用步骤(这里以一个类来监听客户端键盘输入事件为例)
1)定义委托
public delegate void UserRequest(Object sender, EventArgs e);
注意:参数Object用来传递事件的发生者,EventArgs用来传递事件的细节(该例暂时没用到)。
2)定义事件 (步骤1)中的委托类型)
public event UserRequest OnUserRequest;
3)定义监听事件函数
监听事件函数用于在满足一定条件时引发事件,监听事件函数对于在.NET Framwork类库中封装的事件已经为我们定义好了,无需我们自己定义。而这里我们需要自 己定义,此函数相当于事件使用步骤1中的RunWorkerAsync()函数
//该函数不断要求用户从键盘输入字符,如果输入结果为“h”,则引发OnUserRequest事件,事件的引发者是本身(this,即该类(或该类实例化对象)),事件细节无(没有传递任何参数给E//ventArgs实例),我们给这个类起名为UserInputMonitor。 public void run() { bool finished = false; do { if (Console.ReadLine() == "h") { OnUserRequest(this,new EventArgs());//如果用户键盘输入为“h”,则引发事件 } } while (!finished); }
4)定义事件处理程序(这里将该函数定义在客户端类(Client类)中)
首先实例化UserInputMonitor类:
UserInputMonitor monitor = new UserInputMonitor();
然后定义事件处理程序
private void ShowMessage(object sender, EventArgs e) { Console.WriteLine("haha");//如果用户输入的h,则打印“haha” }
5)将事件处理程序绑定到事件(或称订阅事件)
这里将其写到Client的构造函数里
Client(UserInputMonitor m) { m.OnUserRequest += new UserInputMonitor.UserRequest(this.ShowMessage); }
接下来创建客户端并开始监听事件
new Client(monitor);//将事件处理方法绑定到事件OnUserRequest monitor.run();
该自定义事件的全部代码如下所示(c#控制台程序)
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication2 { class Client { //构造函数 Client(UserInputMonitor m) { m.OnUserRequest += new UserInputMonitor.UserRequest(this.ShowMessage);//订阅事件 } public static void Main(string[] args) { UserInputMonitor monitor = new UserInputMonitor(); new Client(monitor);//将事件处理方法绑定到事件OnUserRequest monitor.run(); } private void ShowMessage(object sender, EventArgs e) { Console.WriteLine("haha"); } } class UserInputMonitor { public delegate void UserRequest(Object sender, EventArgs e);//定义委托 public event UserRequest OnUserRequest;//定义此委托类型的事件 public void run() { bool finished = false; do { if (Console.ReadLine() == "h") { OnUserRequest(this,new EventArgs());//如果用户键盘输入为“h”,则事件发生 } } while (!finished); } } }
五. 进一步研究C#中的预定义事件处理机制
可能大家发现在C#中有些事件和前面的似乎不太一样。例如:
private void textBox1_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)//事件处理程序 { } this.textBox1.KeyPress+=newSystem.Windows.Forms.KeyPressEventHandler(this.textBox1_KeyPress);//将事件处理程序绑定到事件KeyPress
这里使用了KeyPressEventArgs而不是EventArgs作为参数。这里使用了KeyEventHandler委托,而不是EventHandler委托。KeyPressEventArgs是EventArgs的派生类,而KeyEventHandler的声明如下 :
public delegate void KeyEventHandler( object sender,KeyEventArgs e);
是参数为KeyEventArgs的委托。那为什么KeyPress事件要这么做呢,我们可以从两个类的构造函数来找答案。
public EventArgs(); public KeyPressEventArgs(char keyChar);
这里的keyChar是什?是用来传递我们按下了哪个键的。我在KeyEventArgs中又发现了如下属性,进一步证明了我的理论。
public char KeyChar { get; }
下面举个具体的例子,加深一下理解:我们也定义一个EventArgs(类似KeyEventArgs)取名MyEventArgs,定义一个构造函数public MyEventArgs(char keyChar),同样我们也设置相应的属性。完成代码如下(控制台程序):
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace ConsoleApplication2 7 { 8 class MyEventArgs:EventArgs//定义了一个事件类 9 { 10 private char keyChar; 11 12 public MyEventArgs(char keyChar)//构造函数 13 { 14 this.keyChar = keyChar; 15 } 16 17 public char KeyChar 18 { 19 get{return keyChar;} 20 } 21 } 22 23 class UserInputMonitor 24 { 25 public delegate void UserRequest(object sender, MyEventArgs e);//定义委托 26 public event UserRequest OnUserRequest;//定义此委托类型的事件 27 28 public void Run()//监听事件函数 29 { 30 bool finished = false; 31 do 32 { 33 string inputString = Console.ReadLine(); 34 if(inputString != "") 35 { 36 OnUserRequest(this,new MyEventArgs(inputString[0]));//引发异常 37 } 38 }while(!finished); 39 } 40 } 41 42 class Client 43 { 44 static void Main(string[] args) 45 { 46 UserInputMonitor monitor = new UserInputMonitor(); 47 new Client(monitor); 48 monitor.Run(); 49 50 } 51 52 Client(UserInputMonitor m) 53 { 54 m.OnUserRequest += new UserInputMonitor.UserRequest(this.ShowMessage);//将监听事件函数绑定到事件处理函数 55 56 } 57 58 public void ShowMessage(object sender, MyEventArgs e) 59 { 60 Console.WriteLine("捕捉到:{0}",e.KeyChar); 61 } 62 } 63 }