委托
当要把方法作为实参传送给其他方法的形参时,形参需要使用委托。委托是一个类型,是一个函数指针类型,这个类型将该委托的实例化对象所能指向的函数的细节封装起来了,即规定了所能指向的函数的签名,也就是限制了所能指向的函数的参数和返回值。当实例化委托的时候,委托对象会指向某一个匹配的函数,实质就是将函数的地址赋值给了该委托的对象,然后就可以通过该委托对象来调用所指向的函数了。
定义委托
语法如下:
delegate result-type Identifier ([parameters]);
说明:
result-type:返回值的类型,和方法的返回值类型一致
Identifier:委托的名称
parameters:参数,要引用的方法带的参数
小结:
当定义了委托之后,该委托的对象一定可以而且也只能指向该委托所限制的函数。即参数的个数、类型、顺序都要匹配,返回值的类型也要匹配。
因为定义委托相当于是定义一个新类,所以可以在定义类的任何地方定义委托,既可以在一个类的内部定义,那么此时就要通过该类的类名来调用这个委托(委托必须是public、internal),也可以在任何类的外部定义,那么此时在命名空间中与类的级别是一样的。
声明一个基于某个委托的事件
Event delegateName eventName;
eventName不是一个类型,而是一个具体的对象,这个具体的对象只能在类A内定义而不能在类A外定义。
在类A中定义一个触发该事件的方法
ReturnType FunctionName([parameters]) { …… If(eventName != null) { eventName([parameters]); 或者eventName.Invoke([parameters]); } …… }
触发事件之后,事件所指向的函数将会被执行。这种执行是通过事件名称来调用的,就像委托对象名一样的。
触发事件的方法只能在A类中定义,事件的实例化,以及实例化之后的实现体都只能在A类外定义。
程序实例
using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Windows.Forms; namespace DelegateStudy { public delegate void DelegateClick (int a); public class butt { public event DelegateClick Click; public void OnClick(int a) { if(Click != null) Click.Invoke(a); //Click(a);//这种方式也是可以的 MessageBox.Show("Click();"); } } class Frm { public static void Btn_Click(int a) { for (long i = 0; i < a; i++) Console.WriteLine(i.ToString()); } static void Main(string[] args) { butt b = new butt(); //在委托中,委托对象如果是null的,直接使用+=符号,会报错,但是在事件中,初始化的时候,只能用+= b.Click += new DelegateClick (Fm_Click); //事件是基于委托的,所以委托推断一样适用,下面的语句一样有效:b.Click += Fm_Click; //b.Click(10);错误:事件“DelegateStudy.butt.Click”只能出现在 += 或 -= 的左边(从类型“DelegateStudy.butt”中使用时除外) b.OnClick (10000); MessageBox.Show("sd234234234"); Console.ReadLine(); } } }
控件事件委托EventHandler
在控件事件中,有很多的委托,在这里介绍一个最常用的委托EventHandler,.NET Framework中控件的事件很多都基于该委托,EventHandler委托已在.NET Framework中定义了。它位于System命名空间:
Public delegate void EventHandler(object sender,EventArgs e);
委托EventHandler参数和返回值
事件最终会指向一个或者多个函数,函数要与事件所基于的委托匹配。事件所指向的函数(事件处理程序)的命名规则:按照约定,事件处理程序应遵循“object_event”的命名约定。object就是引发事件的对象,而event就是被引发的事件。从可读性来看,应遵循这个命名约定。
首先,事件处理程序总是返回void,事件处理程序不能有返回值。其次是参数,只要是基于EventHandler委托的事件,事件处理程序的参数就应是object和EventArgs类型:
第一个参数接收引发事件的对象,比如当点击某个按钮的时候,这个按钮要触发单击事件最终执行这个函数,那么就会把当前按钮传给sender,当有多个按钮的单击事件都指向这个函数的时候,sender的值就取决于当前被单击的那个按钮,所以可以为几个按钮定义一个按钮单击处理程序,接着根据sender参数确定单击了哪个按钮:
if(((Button)sender).Name =="buttonOne")
第二个参数e是包含有关事件的其他有用信息的对象。
控件事件的其他委托
控件事件还有其他的委托,比如在窗体上有与鼠标事件关联的委托:
Public delegate void MouseEventHandler(object sender,MouseEventArgs e);
public event MouseEventHandler MouseDown;
this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown);
private void Form1_MouseDown(object sender, MouseEventArgs e){};
MouseDown事件使用MouseDownEventArgs,它包含鼠标的指针在窗体上的的X和Y坐标,以及与事件相关的其他信息。
控件事件中,一般第一个参数都是object sender,第二个参数可以是任意类型,不同的委托可以有不同的参数,只要它派生于EventArgs即可。
程序实例
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace SecondChangeEvent1 { // 该类用来存储关于事件的有效信息外, // 还用来存储额外的需要传给订阅者的Clock状态信息 public class TimeInfoEventArgs : EventArgs { public TimeInfoEventArgs(int hour,int minute,int second) { this.hour = hour; this.minute = minute; this.second = second; } public readonly int hour; public readonly int minute; public readonly int second; } // 定义名为SecondChangeHandler的委托,封装不返回值的方法, // 该方法带参数,一个clock类型对象参数,一个TimeInfoEventArgs类型对象 public delegate void SecondChangeHandler( object clock, TimeInfoEventArgs timeInformation ); // 被其他类观察的钟(Clock)类,该类发布一个事件:SecondChange。观察该类的类订阅了该事件。 public class Clock { // 代表小时,分钟,秒的私有变量 int _hour; public int Hour { get { return _hour; } set { _hour = value; } } private int _minute; public int Minute { get { return _minute; } set { _minute = value; } } private int _second; public int Second { get { return _second; } set { _second = value; } } // 要发布的事件 public event SecondChangeHandler SecondChange; // 触发事件的方法 protected void OnSecondChange( object clock, TimeInfoEventArgs timeInformation ) { // Check if there are any Subscribers if (SecondChange != null) { // Call the Event SecondChange(clock, timeInformation); } } // 让钟(Clock)跑起来,每隔一秒钟触发一次事件 public void Run() { for (; ; ) { // 让线程Sleep一秒钟 Thread.Sleep(1000); // 获取当前时间 System.DateTime dt = System.DateTime.Now; // 如果秒钟变化了通知订阅者 if (dt.Second != _second) { // 创造TimeInfoEventArgs类型对象,传给订阅者 TimeInfoEventArgs timeInformation = new TimeInfoEventArgs( dt.Hour, dt.Minute, dt.Second); // 通知订阅者 OnSecondChange(this, timeInformation); } // 更新状态信息 _second = dt.Second; _minute = dt.Minute; _hour = dt.Hour; } } } /* ======================= Event Subscribers =============================== */ // 一个订阅者。DisplayClock订阅了clock类的事件。它的工作是显示当前时间。 public class DisplayClock { // 传入一个clock对象,订阅其SecondChangeHandler事件 public void Subscribe(Clock theClock) { theClock.SecondChange += new SecondChangeHandler(TimeHasChanged); } // 实现了委托匹配类型的方法 public void TimeHasChanged( object theClock, TimeInfoEventArgs ti) { Console.WriteLine("Current Time: {0}:{1}:{2}", ti.hour.ToString(), ti.minute.ToString(), ti.second.ToString()); } } // 第二个订阅者,他的工作是把当前时间写入一个文件 public class LogClock { public void Subscribe(Clock theClock) { theClock.SecondChange += new SecondChangeHandler(WriteLogEntry); } // 这个方法本来应该是把信息写入一个文件中 // 这里我们用把信息输出控制台代替 public void WriteLogEntry( object theClock, TimeInfoEventArgs ti) { Clock a = (Clock)theClock; Console.WriteLine("Logging to file: {0}:{1}:{2}", a.Hour.ToString(), a.Minute.ToString(), a.Second.ToString()); } } /* ======================= Test Application =============================== */ // 测试拥有程序 public class Test { public static void Main() { // 创建clock实例 Clock theClock = new Clock(); // 创建一个DisplayClock实例,让其订阅上面创建的clock的事件 DisplayClock dc = new DisplayClock(); dc.Subscribe(theClock); // 创建一个LogClock实例,让其订阅上面创建的clock的事件 LogClock lc = new LogClock(); lc.Subscribe(theClock); // 让钟跑起来 theClock.Run(); } } }
MouseEventHandler和EventHandler传递参数的局限性分析
开发过程中,特别是使用自定义控件时,常常需要对一个控件的click,mouseDown,mouseUp等事件的处理进行重新定义,以满足实际工程应用和要求。常用的方法如下:
button1.Click -= new EventHandler(ButtonClick_Handler); button1.MouseUp -= new MouseEventHandler(ButtonUp_Handler); button1.Click += new EventHandler(ButtonClick_Handler); MouseUp += new MouseEventHandler(ButtonUp_Handler);
可以看到,这里是通过EventHandler和MouseEventHandler这两个委托来能click和mouseup赋值。
这两个委托的定义如下:
EventHandler:
.NET Framework 中的事件模型基于具有事件委托,该委托将事件与事件处理程序连接。引发事件需要两个元素:
标识对事件提供响应的方法的委托。
保存事件数据的类。
public delegate void EventHandler(Object sender, EventArgs e); public event EventHandler NoDataEventHandler;
MouseEventHandler:
表示将处理窗体、控件或其他组件的 MouseDown、MouseUp 或 MouseMove 事件的方法。
委托的原型:
public delegate void MouseEventHandler( Object sender, MouseEventArgs e )
这两个委托都有两个参数,其中Sender可以通过.net的机制来捕获,而EventArgs和MouseEventArgs 该如何使用呢?或者说如何给它赋值?暂时没有办法,还请高人指点。
其实这个问题可以通过匿名委托来解决。
2、使用匿名委托给一些EventHandler/MouseEventHandler的方法传参数
关键代码如下:
public void setSeatButtonMove_EventHandler(CSeatButton seatBtn, Object parentForm) { ///* 常规事件加载方式 */ //seatBtn.button1.Click -= new EventHandler(seatButtonClick_Handler); //seatBtn.button1.MouseUp -= new MouseEventHandler(seatButtonUp_Handler); //seatBtn.button1.Click += new EventHandler(seatButtonClick_Handler); //seatBtn.button1.MouseUp += new MouseEventHandler(seatButtonUp_Handler); /* 匿名事件加载方式 */ seatBtn.button1.Click -= delegate(Object o, EventArgs e) { seatButtonClick_Handler(seatBtn.button1, parentForm); }; seatBtn.button1.MouseUp -= delegate(Object o, MouseEventArgs e) { seatButtonUp_Handler(seatBtn.button1, parentForm); }; seatBtn.button1.Click += delegate(Object o, EventArgs e) { seatButtonClick_Handler(seatBtn.button1, parentForm); }; seatBtn.button1.MouseUp += delegate(Object o, MouseEventArgs e) { seatButtonUp_Handler(seatBtn.button1, parentForm); }; } public void seatButtonClick_Handler(object sender,object formOfSender) { string formName = ((Form)formOfSender).Name.Trim(); if (formName.Equals("Form1")) { MessageBox.Show("In Form1,click a button!"); } if (formName.Equals("Form2")) { MessageBox.Show("In Form2,click a button!"); } }
小结
(1)、在定义事件的那个类A里面,可以任意的使用事件名,可以触发;在别的类里面,事件名只能出现在 += 或-= 的左边来指向函数,即只能实例化,不能直接用事件名触发。但是可以通过A类的对象来调用A类中的触发事件的函数。这是唯一触发事件的方式。
(2)、不管是多播委托还是单播委托,在没有特殊处理的情况下,在一个线程的执行过程中去调用委托(委托对象所指向的函数),调用委托的执行是不会新起线程的,这个执行还是在原线程中的,这个对于事件也是一样的。当然,如果是在委托所指向的函数里面去启动一个新的线程那就是另外一回事了。
(3)、事件是针对某一个具体的对象的,一般在该对象的所属类A中写好事件,并且写好触发事件的方法,那么这个类A就是事件的发布者,然后在别的类B里面定义A的对象,并去初始化该对象的事件,让事件指向B类中的某一个具体的方法,B类就是A类事件的订阅者。当通过A类的对象来触发A类的事件的时候(只能A类的对象来触发A类的事件,别的类的对象不能触发A类的事件,只能订阅A类的事件,即实例化A类的事件),作为订阅者的B类会接收A类触发的事件,从而使得订阅函数被执行。一个发布者可以有多个订阅者,当发布者发送事件的时候,所有的订阅者都将接收到事件,从而执行订阅函数,但是即使是有多个订阅者也是单线程。
如对本文有疑问,请提交到交流论坛,广大热心网友会为你解答!! 点击进入论坛