您的位置:首页 > 其它

事件与委托

2008-01-18 15:50 246 查看
事件与委托趣谈
事件与委托似乎很难以理解,这是因为它们的使用方式与常用的编码有很大的差别,例如通常编写的都是同步代码,调用一个类型的方法,会即刻出现方法执行的结果,这是符合逻辑的。但在某些情况中,同步代码未必满足需求,拿公共汽车来打个比方,如果交通管制中心希望每一辆公车到达一个站点时都发送给自己一个信号以便自己能够随时掌握交通状况,使用同步代码,公汽对象肯定需要调用管制中心对象,这样就出现了我们一直不愿意看到的情况:两个类型紧密地耦合在一起。既然要其它类型对自己的行为作出反应,亲自调用其类型的方法似乎不可避免,在同步代码中,很难避免这种紧密的类型调用关系。
另一个差别是在一般情况下,我们只将属性作为参数传递给方法,而很少会考虑将一个方法传递给另一个方法。
我们抛弃各种C#参考书中桀骜难懂的事件与委托概念,设想一个情景来理解事件与委托的使用:有一家IT公司,董事长不希望自己的雇员在上班时间玩游戏,但又不可能每时每刻都盯着每个雇员,因此,他希望使用一种新的方式实现监视雇员的效果:如果有雇员违反规定,某个设备或专门的监查人员将自动发出一个消息通知他,董事长只需要在事情发生时进行处理。
因此,这个用例实际上是两种类型——董事长类与雇员类——之间的交互,下面的代码将给读者展示如何使用委托与事件机制实现这种交互:
首先,我们需要在董事长类与雇员类之间定义一个委托类型,用于传递两者之间的事件,这个类型就是一个监视设备或专门负责打小报告的监查人员:
public delegate void DelegateClassHandle();
定义一个委托的过程类似方法的定义,但它没有方法体。定义委托一定要添加关键字delegate。由于定义委托实际上相当一个类,因此可以在定义类的任何地方定义委托。另外,根据委托的可见性,也可以添加一般的访问修饰符,如public、private和protected。
委托的返回值类型为void,这并非表示委托类型本身带有返回值,该返回值类型是指委托的目标函数类型,即它委托的一个事件处理函数返回值是void类型。
新建一个雇员类Employee,其代码如下:
public class Employee
{
public event DelegateClassHandle PlayGame;

public void Games()
{
if (PlayGame != null)
{
PlayGame();
}
}
}
雇员类Employee代码中定义了一个DelegateClassHandle类型的事件PlayGame,它的定义方式也很特殊,首先必须使用关键字event,表示PlayGame是一个事件,同时还必须声明该事件的委托类型为DelegateClassHandle,即将来由该类型的委托对象负责通知事件。
如果有雇员开始玩游戏,它将执行Games方法,而只要该方法一被调用,就会触发一个事件PlayGame,然后董事长就会收到这个事件的消息——有人在玩游戏了。
董事长类代码如下,他有一个方法Notify用于接收消息:
public class Admin
{
public void Notify()
{
System.Console.WriteLine("someone is playing game");
}
}
Employee的PlayGame事件如何与Admin的Notify方法关联起来呢?只需通过事件绑定即可实现,具体过程如下列代码:
Employee employee = new Employee();
Admin admin = new Admin();
employee.PlayGame += new DelegateClassHandle(admin.Notify);
employee.Games();
请大家注意事件绑定的代码:
employee.PlayGame += new DelegateClassHandle(admin.Notify);
通过DelegateClassHandle将两个类的交互进行了绑定,当employee.Games方法调用后,触发PlayGame事件,而该事件将被委托给admin的Notify方法处理,通知董事长有雇员在上班时间玩游戏。
但董事长并不满足这种简单的通知,他还想知道究竟是谁在上班时间违反规定。显然,现在委托对象必须传递必要的参数才行,这个要求也可以很容易地办到。事件的参数可以设置为任何类型的数据,在.NET框架中,还提供了事件参数基类EventArgs专门用于传递事件数据。
从该EventArgs类派生一个自定义的事件参数类CustomeEventArgs,这个类型将携带雇员姓名和年龄信息:
public class CustomeEvetnArgs : EventArgs
{
string name = "";
int age = 0;
public CustomeEvetnArgs()
{ }
public string Name
{
get { return this.name; }
set { this.name = value; }
}
public int Age
{
get { return this.age; }
set { this.age = value; }
}
}
修改委托类型DelegateClassHandle的定义,让其携带必要的参数:
public delegate void DelegateClassHandle(object sender, CustomeEvetnArgs e);
雇员类的代码修改后如下:
public class Employee
{
private string _name;

public string Name
{
get { return _name; }
set { _name = value; }
}
private int _age;

public int Age
{
get { return _age; }
set { _age = value; }
}

public event DelegateClassHandle PlayGame;

public void Games()
{
if (PlayGame != null)
{
CustomeEvetnArgs e = new CustomeEvetnArgs();
e.Name = this._name ;
e.Age = this._age;
PlayGame(this, e);
}
}
}
在Games方法中,首先新建一个CustomeEventArgs对象,然后设置了必要的属性Name和Age。
董事长的通知方法也必须相应地进行修改:
public class Admin
{
public void Notify(object sender, CustomeEvetnArgs e)
{
System.Console.WriteLine(e.Name+" is "+e.Age.ToString());
}
}
将两个类型对象进行关联的代码也需要进行相应的修改:
Employee employee = new Employee();
employee.Name = "Mike";
employee.Age = 25;
Admin admin = new Admin();
employee.PlayGame += new DelegateClassHandle(admin.Notify);
employee.Games();
修改后的代码运行的结果是,当Mike调用Games方法玩游戏时,会自动触发PlayGame事件,而该事件携带相关信息通知admin,后者的Notify方法将接收到数据并输出“Mike is 25”,告诉董事长是Mike,25岁,正在上班时间玩游戏。
委托是可以多路广播(Mulitcast)的,即一个事件可以委托给多个对象接收并处理。在上面的用例中,如果有另一位经理与董事长具有同样的癖好,也可以让委托对象将雇员的PlayGame事件通知他。
首先定义经理类:
public class Manager
{
public void Notify(object sender, CustomeEvetnArgs e)
{
System.Console.WriteLine(sender.ToString() + "-" + e.Name);
}
}
经理Manager类型的Notify方法与Admin一致,他也接受到相应的信息。
委托的多路广播绑定的方法仍然是使用+=运算符,其方法如下面的代码所示:
Employee employee = new Employee();
employee.Name = "Mike";
employee.Age = 25;
Admin admin = new Admin();
Manager manager = new Manager();

employee.PlayGame += new DelegateClassHandle(admin.Notify);
employee.PlayGame += new DelegateClassHandle(manager.Notify);
employee.Games();
执行该方法,读者将看到admin和manager的Notify方法都会被事件通知并调用执行。通过这样的方法,董事长和经理都会知道Mike在玩游戏了。
如果董事长不希望经理也收到这个通知,该如何解除PlayGame对manager的事件绑定呢?同样非常简单,在employee.Games方法被调用前执行下列语句即可:
employee.PlayGame -= new DelegateClassHandle(manager.Notify);
最后需要提醒读者注意的,Employee类中的Games方法在触发事件PlayGame之前需要判断该事件是否为null。当employee对象的Games方法触发事件PlayGame后,必须有一个目标函数来处理这个事件,而该语句正是判断该目标函数是否存在。如果将这个判断去掉,且对事件不进行任何绑定而直接调用Games方法,程序将在事件PlayGame处弹出一个NullReferenceException的异常。
读者能够从委托与事件的代码中得出什么结论吗?两个需要存在调用关系的类型,在各自的实现中却没有编写实际的调用代码,它们只是通过一个事件和一个第三方的委托类型完成了消息的传递过程。两个类型之间不存在任何的紧密耦合,它们看似松散地通过一个委托对象中通信,实现了本书一直宣传的“高聚合”和“低耦合”观点。

对ASP.NET中关于事件委托的理解
事件委托的概念
.NET框架的事件委托遵循特定的签名和命名约定。这种约定依赖于可视化设计工具,为客户端代码提供了一致性的模型。为了理解这种约定,下面来看看.NET框架中一个常见的可访问事件委托System.EventHandler:
public delegate void EventHandler(object sender,EventArgs e);
下面是一些特定的事件委托签名约定:
◎ 事件委托的返回类型是void
◎ 一个事件委托带有两个参数。第一个参数是object类型,表示事件的发送者。第二个参数
描述事件的数据,是由System.EventArgs派生出的类的实例。
应该根据.NET框架的命名约定来给事件数据类和事件委托命名。事件数据类由事件名再添加
后缀EventArgs构成,如MonthChangeEventArgs。
事件委托由事件名再添加EventHandler后缀构成,如MonthChangeEventHandler。事件委托
用事件处理程序(Event Handler)命名是因为她们将绑定到处理事件的方法上。
理解二:Wiring事件
将事件处理程序和事件相关联的过程(添加委托给invocation列表)叫做事件布线(event wiring).
而从事件中删除事件处理程序的过程叫做事件撤线(event unwring)。
在C#中,对于一个事件的布线和撤线事件处理程序的语法如下:
button.Click += new EventHandler(this.Button_Clicked);
button.Click -= new EventHandler(this.Button_Clicked) ;
这里,button是Button控件的一个实例,并建立于具有Button_Clicked方法的类中,该方法处理按钮的Click事件。
理解三:事件委托的实现
为了在类中实现事件,需要一个事件数据的类、事件委托、在类中拥有invocation列表的委托成员,以及一个发布事件通知的方法。
具体实现过程如下:
1)如果类没有任何关联的事件数据,对事件数据使用EventArgs类。或者可以使用其他事先已经存在的事件数据类。如果不存在一个合适的事件数据类,则定义一个事件来包含事件数据。这个类必须从System.EventArgs中派生。按照规则它的名字应该是事件名加上EventArgs得到。例如,AdCreatedEventArgs,MonthChangedEventArgs.
下面的代码声明了一个事件数据类:
public class LowChargeEventArgs:EventArgs{...}
2) 如果事件没有关联数据,使用第一步的EventArgs,用System.EventHandler作为事件委托或者使用能匹配事件的预先存在的其他委托。如果不存在一个合适的事件委托,则定义一个事件委托,该委托的第二个参数具备来自第一步的事件数据类的类型。根据规则,事件委托的名字是在事件后附加EventHandler.例如,AdCreateEventHandler,MonthChangedEventHandler。下面的代码用来定义事件委托:
public delegate void LowChargeEventHandler(object sender,LowChargeEventArgs e);
3) 在类里,用event关键字定义事件成员。将事件名传递给事件成员。成员的类型是第二步中事件委托的类型。
如下面的例子:
public event LowChargeEventHandler LowCharge;
事件成员包含订阅事件的委托的列表。当调用这个成员时,它通过调用委托来分配事件。

4) 在类里,定义一个受保护的虚拟方法,在检查是否有事件监听器存在之后调用事件委托。其参数就是第一步中定义的事件数据×EventArgs。方法的名字是在事件名前加上前缀On。例如:
protected virtual void OnLowCharge(LowChargeEventArgs e){
if (LowCharge != null){
LowCharge(this,e);
}
}

On<EventName>方法的目的是用来通知事件订阅者。

委托释疑
一个函数带有参数非常平常,但一个类带有一个方法的参数可能会觉得奇怪,这就是委托的特征。很多书籍都说委托就象C或C++的函数指针,说得不无道理,因为它允许在委托对象的内部封装方法的引用,也就是前面所说的带有一个方法的参数,这个方法参数有一定的规则,它可以是静态的,也可以是一个实例方法,这个方法的参数和返回类型必须和委托对象所带的相同。
委托就像一个中间代理,通过引用这个委托对象,就可以调用被这个委托对象所引用的方法,也就是它所带的方法参数。可以调用它引用的方法,而不用管这个方法的哪个类的,因为已经通过这个委托对象去引用这个类的方法了,即我们调用这个方法是通过这个委托中间代理来实现的。有一点好处就是委托它是面向对象的,而且类型安全的。看以下代码:
class 委托人
{
public string invokedmethod() //注意此方法返回string,与委托对象相同,所要委托的事
{
Console.WriteLine("Hello");
}
}
class 类
{
委托人 classobj = new 委托人();
delegate string 被委托人(); //声明一个返回类型为string的委托对象
被委托人 deleobj=new 被委托人(classobj.invokedmethod);//创建一个带有方法的委托对象
deleobj();
}
委托通常与事件一起结合使用。事件是对用户动作的处理,当用户有某种动作的时候,通过委托对象来执行,因此,事件是通过委托来声明的。事件可以使用任何类型的委托,但有一点要注意,就是在使用组件时要遵循一些规则:事件使用的委托类型应有两个参数,一个是对象源,一个是e参数。对象源表示事件发生的源 ; e表示封装该事件的一些信息,这个e是从EventArgs类派生出来的。事件处理程序其实就同系统已定义的按钮点击事件,只不过这一次是由我们自己来定义而已。关于委托与事件的例子,参照http://blog.csdn.net/jabby12/archive/2004/08/02/58901.aspx
(注:很多人说对委托的概念很难理解,确实MSDN上的解释不够容易理解,听起来有点抽象,因此我写在这篇文章时尽量站在用户的角度而非程序员的角度去把委托的概念解释清晰)

C#中的事件
目录:
介绍委托和事件
声明事件(定义事件)
注册事件
实现事件
触发事件
为事件增加“添加/删除”方法
一 介绍委托和事件
事件是特殊化的委托,委托是事件的基础,所以在介绍事件之前先介绍一下委托。
委托:使用委托可以将方法应用(不是方法)封装在委托对象内,然后将委托对象传递给调用方法的代码,这样编译的时候代码就没有必要知道调用哪个方法。通过使用委托程序能够在运行时动态的调用不供的方法。
事件:事件是对象发送的消息,发送信号通知客户发生了操作。这个操作可能是由鼠标单击引起的,也可能是由某些其他的程序逻辑触发的。事件的发送方不需要知道那个对象或者方法接收它引发的事件,发送方只需知道在它和接收方之间存在的中介(Deletgate)
二. 声明事件(定义事件)
在GUI图形界面中的事件(单击按钮或者选择菜单等)是已经定义好的事件,所需要的就是注册事件。我们也可以自己定义声明事件,可以将自己定义的事件应用到程序的任何地方。事件和委托是分不开的,c# 中使用委托来声明事件。
如果在类里面声明事件,那么必须先声明该事件的委托类型,也可以使用一个事先已经声明好的委托类型。
下面是一个声明事件的例子:
using system;
public delegate void MenuHandler()//事先申明一个委托,也可以在类的内部声明委托类型
public class Menuitem //声明一个菜单项目的类
{
public event MenuHadler MenuSelection;// 声明一个事件并指定它的委托类型
string text ;//声明一个字符串
public MenuItem(string text) //菜单项目类的构造函数
{
this.text=text; // 初始化text字符串
}
public void Fird()
{
MenuSelection; //定义一个触发事件的方法(后面将用到)
}
public string Text //定义属性
{
get
{
return text;
}
set
{
text=value;
}
}

}
程序首先定义个一个叫做MenuHandler的委托,然后定义了叫做 MenuItem 的类,MenuItem类包含了一个MenuSelection 事件(菜单被选择事件),事件的委托类型是MenuHandler。事件的声明完成啦,下面开始注册事件
三.注册事件
这里先讲一下 publisher/subscriber(发布者/订阅者)模式,因为事件就是从这个模式继承下来的。一個訊息的傳送者 (Sender) 稱之為 Publisher,而該訊息的接收者 (Receiver),則稱之為Subscriber,Subscriber(订阅者)在Publisher(发布者)处注册自己,Publisher发生改变的时候,通知Subscriber做适当的操作,很类似设计模式中的Observer 。
在这里我们认为注册的程序就是订阅者(Subscriber)。事件相当于发布者(Publisher),下面的程序表明了如何把订阅器连接到发布器:
using system;
public class DelegateAndEvents //定义个注册事件的类
{
public static void int Main ()
{
SiteManager sm=new SiteManager (); // SiteManager是一个站点管理类
MenuItem AddMenu= new MenuItem(“Add”) //定义一个新的事件类
MenuItem DelMenu=new MenuItem(“Delete”) //定义另外一个新的事件类

//通过委托,sm类的Addsite 方法 注册到事件中
AddMenu.MenuSelelction+=new MenuHandler(sm.AddSite)
//通过委托,sm类的DeleteSite 方法 注册到事件中
DelMenu.MenuSelelction+=new MenuHandler(sm. DeleteSite)
}
}
从注册事件的那行代码来看,事件就像是事件类的一个字段(属性),但是对它的访问是很受限制的,只能通过:在该字段上撰写新的委托(+=)或者从字段(可能是复合字段)移除委托(-=)进行访问,
移除注册事件:DelMenu.MenuSelelction-=new MenuHandler(sm. DeleteSite)
四.实现事件
这里就要讲到上个例子中的SiteManager类,它包含了事件需要实现的方法(AddSite,DeleteSite),SiteManager类的方法一定要符合事件的委托类型和返回类型,在委托添加到事件前,该方法能够以这个方式付给委托。
using system;
public class SiteManger //站点管理类
{
public SiteManger() //构造函数 初始化变量
{
//impelement something
}
public void AddSite() //方法要符合委托的类型
{
// impelement 执行添加站点的操作代码
}
public void DeleteSite() //方法要符合委托的类型
{
// impelement 执行删除站点的操作代码
}
}
SiteManger类的方法要符合委托类型,这样他的方法可以很方便的当作事件的方法使用。

五. 触发事件
当调用事件时就可以说时触发了事件。事件是从定义它的类中触发的
示例
using system;
public class menu //定义一个菜单类
{
public menu(strng title) //类的构造函数
{
//初始化变量
}
public void Run() //触发事件的过程
{
//声明一个菜单项目对象(menuItem前面已经定义)
menuItem myMenuItem=new menuItem }
myMenuItem.fire //调用fire 方法触发事件,事件执行SiteManger类的方法
}
到此为止我就完成了整个事件的实现过程。
六.为事件增加“添加/删除”方法
如果由大量的方法需要注册到事件时,可以为为事件增加 Add,Remove 方法。通过访问事件的“添加/删除方法”来注册方法
示例:
using sytem;
pulic Delete ovid MenuHandler(object sender,EventArgs e); //示例1的变形
public class MenuItem
{
int numberof Events;
string tex;
private MenuHandler mh=null; //内部得一个委托
public event MenuHandler MenuSelecton
{
add //增加“添加”方法
{
mh+=value;
nuberofevents++;
}
remove
{
mh-=value;
numberofevents--;
}
}
}

委托释疑
一个函数带有参数非常平常,但一个类带有一个方法的参数可能会觉得奇怪,这就是委托的特征。很多书籍都说委托就象C或C++的函数指针,说得不无道理,因为它允许在委托对象的内部封装方法的引用,也就是前面所说的带有一个方法的参数,这个方法参数有一定的规则,它可以是静态的,也可以是一个实例方法,这个方法的参数和返回类型必须和委托对象所带的相同。
委托就像一个中间代理,通过引用这个委托对象,就可以调用被这个委托对象所引用的方法,也就是它所带的方法参数。可以调用它引用的方法,而不用管这个方法的哪个类的,因为已经通过这个委托对象去引用这个类的方法了,即我们调用这个方法是通过这个委托中间代理来实现的。有一点好处就是委托它是面向对象的,而且类型安全的。看以下代码:
class invokedclass
{
public string invokedmethod() //注意此方法返回int,与委托对象相同
{
Console.WriteLine("Hello");
}
}
invokedclass classobj = new invokeclass();
delegate string MyDelegate(); //声明一个返回类型为int的委托对象
MyDelegate deleobj=new MyDelegate(classobj.invokedmethod);//创建一个带有方法的委托对象
委托通常与事件一起结合使用。事件是对用户动作的处理,当用户有某种动作的时候,通过委托对象来执行,因此,事件是通过委托来声明的。事件可以使用任何类型的委托,但有一点要注意,就是在使用组件时要遵循一些规则:事件使用的委托类型应有两个参数,一个是对象源,一个是e参数。对象源表示事件发生的源 ; e表示封装该事件的一些信息,这个e是从EventArgs类派生出来的。事件处理程序其实就同系统已定义的按钮点击事件,只不过这一次是由我们自己来定义而已。关于委托与事件的例子如下:
//********************************************/
/**//* Delegate Sample
/*
//******************************************************************/

using System;

namespace EventTest
{
//事件类
public class EventClass
{
public void display(object sender,System.EventArgs e)
{
Console.WriteLine("This is the Event Class");
Console.ReadLine();
}
}
//调用类
class InvokeClass
{
//声明代理对象,注意参数
public delegate void delegateobj(object sender,System.EventArgs e);
//声明事件对象
private event delegateobj obj;
//声明要调用的事件类对象
private EventClass ec;

public InvokeClass()
{
ec=new EventClass();
//添加事件对象到事件队列中,参数为要调用的事件
this.obj+=new delegateobj(ec.display);
}
//调用delegate对象触发事件
protected void OnObj(System.EventArgs e)
{
if(this.obj!=null)
{
obj(this,e);
}
}
public void RaiseEvent()
{
EventArgs e=new EventArgs();
OnObj(e);
}
static void Main(string[] args)
{
InvokeClass ic=new InvokeClass();
Console.WriteLine("Please input a string");
string input=Console.ReadLine();
if(input.Equals(""))
{
Console.WriteLine("Sorry,you don't input anything");
}
else
{
//触发事件
ic.RaiseEvent();
}
}
}
}
(注:很多人说对委托的概念很难理解,确实MSDN上的解释不够容易理解,听起来有点抽象,因此我写在这篇文章时尽量站在用户的角度而非程序员的角度去把委托的概念解释清晰)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: