在发生其他类或对象关注的事情时,类或对象可通过事件通知它们。发送(或引发)事件的类称为“发行者”,接收(或处理)事件的类称为“订户”。
事件概述
事件具有以下特点:
l 发行者确定何时引发事件,订户确定执行何种操作来响应该事件。
l 一个事件可以有多个订户。一个订户可处理来自多个发行者的多个事件。
l 没有订户的事件永远不会被调用。
l 事件通常用于通知用户操作(如:图形用户界面中的按钮单击或菜单选择操作)。
l 如果一个事件有多个订户,当引发该事件时,会同步调用多个事件处理程序。
l 可以利用事件同步线程。
l 在 .NET Framework 类库中,事件是基于 EventHandler 委托和 EventArgs 基类的。
事件的实现原理
事件是对象发送的消息,以发信号通知操作的发生。操作可能是由用户交互(例如鼠标单击)引起的,也可能是由某些其他的程序逻辑触发的。引发事件的对象称为事件发送方。捕获事件并对其作出响应的对象叫做事件接收方。
在事件通信中,事件发送方类不知道哪个对象或方法将接收到(处理)它引发的事件。所需要的是在源和接收方之间存在一个媒介(或类似指针的机制)。.NET Framework 定义了一个特殊的类型(Delegate),该类型提供函数指针的功能。
委托是可保存对方法的引用的类。与其他的类不同,委托类具有一个签名,并且它只能对与其签名匹配的方法进行引用。这样,委托就等效于一个类型安全函数指针或一个回调。虽然委托具有许多其他的用途,但这里只讨论委托的事件处理功能。一个委托声明足以定义一个委托类。声明提供委托的签名,公共语言运行库提供实现。
事件委托是多路广播的,这意味着它们可以对多个事件处理方法进行引用。委托考虑了事件处理中的灵活性和精确控制。通过维护事件的已注册事件处理程序列表,委托为引发事件的类担当事件发送器的角色。
事件是特殊类型的多路广播委托,仅可从声明它们的类或结构(发行者类)中调用。如果其他类或结构订阅了该事件,则当发行者类引发该事件时,会调用其事件处理程序方法。
EventHandler 委托表示将处理不包含事件数据的事件的方法。
[SerializableAttribute]
[ComVisibleAttribute(true)]
public delegate void EventHandler (
Object sender,
EventArgs e
)
sender :事件源。
e :不包含任何事件数据的 EventArgs。
事件处理程序委托的标准签名定义一个没有返回值的方法,其第一个参数的类型为 Object,它引用引发事件的实例,第二个参数从 EventArgs 类型派生,它保存事件数据。如果事件不生成事件数据,则第二个参数只是 EventArgs 的一个实例。否则,第二个参数为从 EventArgs 派生的自定义类型,提供保存事件数据所需的全部字段或属性。
EventHandler 是一个预定义的委托,专用于表示不生成数据的事件的事件处理程序方法。如果事件生成数据,则必须提供自己的自定义事件数据类型,并且必须要么创建一个委托,其中第二个参数的类型为自定义类型,要么使用泛型EventHandler 委托类并用自定义类型替代泛型类型参数。
若要将事件与处理事件的方法关联,请向事件添加委托的实例。除非移除了该委托,否则每当发生该事件时就调用事件处理程序。
EventArgs 是包含事件数据的类的基类。
此类不包含事件数据,在事件引发时不向事件处理程序传递状态信息的事件会使用此类。如果事件处理程序需要状态信息,则应用程序必须从此类派生一个类来保存数据。
例如,System.AssemblyLoadEventArgs 类用于保存程序集加载事件的数据,并包含描述所加载程序集的 System.Reflection.Assembly。
以编程方式订阅事件
定义一个事件处理程序方法,其签名与该事件的委托签名匹配。例如,如果事件基于 EventHandler 委托类型,则下面的代码表示方法存根:
void HandleCustomEvent(object sender, CustomEventArgs e)
{
// Do something useful here.
}
使用加法赋值运算符 (+=) 来为事件附加事件处理程序。在下面的示例中,假设名为 publisher 的对象拥有一个名为 RaiseCustomEvent 的事件。请注意,订户类需要引用发行者类才能订阅其事件。
publisher.RaiseCustomEvent += HandleCustomEvent;
请注意,上面的语法是 C# 2.0 中的新语法。它完全等效于 C# 1.0 语法,必须使用以下新关键字显式创建封装委托:
publisher.RaiseCustomEvent += new CustomEventHandler(HandleCustomEvent);
使用加法赋值运算符 (+=) 来为事件附加匿名方法。在下面的示例中,假设名为publisher 的对象拥有一个名为 RaiseCustomEvent 的事件,并且还定义了一个CustomEventArgs 类以承载某些类型的专用事件信息。请注意,订户类需要引用publisher 才能订阅其事件。
publisher.RaiseCustomEvent += delegate(object o, CustomEventArgs e)
{
string s = o.ToString() + " " + e.ToString();
Console.WriteLine(s);
};
请注意,如果您是使用匿名方法订阅的事件,该事件的取消订阅过程就比较麻烦。此时要取消订阅,请返回到该事件的订阅代码,将该匿名方法存储在委托变量中,然后将委托添加到该事件中。
取消订阅
要防止在引发事件时调用事件处理程序,您只需取消订阅该事件。要防止资源泄露,请在释放订户对象之前取消订阅事件,这一点很重要。在取消订阅事件之前,在发布对象中作为该事件的基础的多路广播委托会引用封装了订户的事件处理程序的委托。只要发布对象包含该引用,就不会对订户对象执行垃圾回收。
使用减法赋值运算符 (-=) 取消订阅事件:
publisher.RaiseCustomEvent -= HandleCustomEvent;
所有订户都取消订阅某事件后,发行者类中的事件实例会设置为 null。
自定义事件的实现
以下的内容参考了sam1111的博客文章,地址为:
http://blog.csdn.net/sam1111/archive/2002/04/15/9773.aspx
C#中的事件处理实际上是一种具有特殊签名的delegate,像下面这个样子:
public delegate void MyEventHandler(object sender, MyEventArgs e);
其中的两个参数,sender代表事件发送者,e是事件参数类。MyEventArgs类用来包含与事件相关的数据,所有的事件参数类都必须从System.EventArgs类派生。当然,如果你的事件不含参数,那么可以直接用System.EventArgs类作为参数。
自定义事件的实现可以归结为以下几步:
1.定义事件参数类,此类应当从System.EventArgs类派生。如果事件不带参数,这一步可以省略,可直接转至步骤3。在发行者类和订户类均可看见的范围中声明类,并添加保留自定义事件数据所需的成员。
2.声明一个委托,它有两个参数,第一个参数是事件发送者对象,第二个参数是事件参数类对象。
3. 在发布类中声明事件。用event关键字定义事件对象,它同时也是一个delegate对象。
4. 在发布类中需要触发事件的地方用调用delegate的方式写事件触发方法。一般来说,此方法应为protected访问限制,既不能以public方式调用,但可以被子类继承。名字是OnEventName。
5.在订阅者类中定义事件处理方法,它应当与delegate对象具有相同的参数和返回值类型。
6.在订阅者类中用+=操作符添加事件到事件队列中(-=操作符能够将事件从队列中删除)。
7.在适当的地方调用事件触发方法触发事件。
下面是一个简单的例子:
using System;
namespace Event
{
//步骤1,定义事件参数类
public class MyEventArgs : EventArgs
{
private string message;
///
/// 信息
///
public string Message
{
get { return message; }
}
//构造函数
public MyEventArgs(string message)
{
this.message = message;
}
}
//步骤2,定义delegate对象
public delegate void MyEventHandler(object sender, MyEventArgs e);
///
/// 事件发布者
///
class Publisher
{
//步骤3,定义事件对象
public event MyEventHandler MyEvent;
public void RaiseEvent()
{
MyEventArgs e = new MyEventArgs("Hello,World!");
//步骤4,触发事件
if (MyEvent != null)
{
MyEvent(this, e);
}
}
#region
//上面的RaiseEvent方法还可以用下面的代码替换,
//以便子类能够继承/Override触发事件的方法,在这里指OnMyEvent方法
//public void RaiseEvent()
//{
// MyEventArgs e = new MyEventArgs("Hello,World!");
// OnMyEvent(e);
//}
//protected void OnMyEvent(MyEventArgs e)
//{
// if (MyEvent != null)
// {
// MyEvent(this, e);
// }
//}
#endregion
}
///
/// 事件订阅者
///
class Subscriber
{
private string id;
public Subscriber(string id, Publisher publisher)
{
this.id = id;
//步骤6.用+=操作符添加事件到事件队列中(-=操作符能够将事件从队列中删除)。
publisher.MyEvent += new MyEventHandler(publisher_MyEvent);
}
//步骤5,定义事件处理方法
void publisher_MyEvent(object sender, MyEventArgs e)
{
Console.WriteLine(id + " received this message: {0}", e.Message);
}
}
class Program
{
static void Main(string[] args)
{
Publisher pub = new Publisher();
Subscriber sub1 = new Subscriber("sub1", pub);
Subscriber sub2 = new Subscriber("sub2", pub);
//步骤7.在适当的地方调用事件触发方法触发事件。
pub.RaiseEvent();
//Keep the console window open
Console.WriteLine("Press Enter to close this window.");
Console.ReadLine();
}
}
}