这个指南向你展示在C#中怎样声明、调用和关连(hook up)事件(Event).
在C#中,当一个类发生了某个对象感兴趣的事情的时候,那么就会提供一个事件,把发生的事情通知给使用这个类的对象。它和图形用户接口中使用的事件非常相似,典型的,在可视的控件的类的接口中,当用户对这些控件做了某些操作的时候,都会向这个控件发出通知事件,如点击一个按钮(button)。
但是,事件不仅仅在图形用户接口中使用,事件(Events)为对象提供了一种普通的方法,把这个对象的状态的变化以信号的方式发送给它的使用者。事件对于创建能够在不同程序间可复用的代码非常重要。
事件是使用委托(delegates)来声明的,如果你没有学习过委托,那么开始学习下面的内容之前,需要先学习有关委托的内容(注:关于委托可以参考msdn中委托指南中的讲解,http://msdn.microsoft.com/en-us/library/aa288459(VS.71).aspx)。一个委托封装了一个回调方法,以便它能够被匿名调用。对于类来说,事件允许把这个类的使用者的一些方法(Method)委托给它,在类的事件发生的时候,那些被委托的方法将会被调用。也就是说,在类的事件发生时,会调用类的使用者所委托的方法。
在这篇指南中除了提供事件的声明、调用和关连的例子以外,还对下列标题进行了介绍。
・ 事件和继承
・ 接口中的事件
・ .NET Framework 标准
例1
下面是一个简单的例子,类ListWithChangedEvent与标准的ArrayList类相似,每当List中的内容发生改变时,Changed事件就会被调用触发。像这样通用的类能够被很多程序中大多数方法使用。
例如,一个文字处理器,可能维护打开的文档的列表,每当这个列表的内容发生变化时,在这个文字处理器中的很多不同的对象可能都需要被通知到,以便用户接口能够被更新。通过使用事件,维护文档列表的List不必知道谁需要被通知(在列表内容发生变化时),事件会自动的调用并且把当前的变化通知给每个需要的对象。通过使用事件,程序的模块化被增强了。
- // events1.cs
- using System;
- namespace MyCollections
- {
- using System.Collections;
- // A delegate type for hooking up change notifications.
- public delegate void ChangedEventHandler(object sender, EventArgs e);
- // A class that works just like ArrayList, but sends event
- // notifications whenever the list changes.
- public class ListWithChangedEvent: ArrayList
- {
- // An event that clients can use to be notified whenever the
- // elements of the list change.
- public event ChangedEventHandler Changed;
- // Invoke the Changed event; called whenever list changes
- protected virtual void OnChanged(EventArgs e)
- {
- if (Changed != null)
- Changed(this, e);
- }
- // Override some of the methods that can change the list;
- // invoke event after each
- public override int Add(object value)
- {
- int i = base.Add(value);
- OnChanged(EventArgs.Empty);
- return i;
- }
- public override void Clear()
- {
- base.Clear();
- OnChanged(EventArgs.Empty);
- }
- public override object this[int index]
- {
- set
- {
- base[index] = value;
- OnChanged(EventArgs.Empty);
- }
- }
- }
- }
- namespace TestEvents
- {
- using MyCollections;
- class EventListener
- {
- private ListWithChangedEvent List;
- public EventListener(ListWithChangedEvent list)
- {
- List = list;
- // Add "ListChanged" to the Changed event on "List".
- List.Changed += new ChangedEventHandler(ListChanged);
- }
- // This will be called whenever the list changes.
- private void ListChanged(object sender, EventArgs e)
- {
- Console.WriteLine("This is called when the event fires.");
- }
- public void Detach()
- {
- // Detach the event and delete the list
- List.Changed -= new ChangedEventHandler(ListChanged);
- List = null;
- }
- }
- class Test
- {
- // Test the ListWithChangedEvent class.
- public static void Main()
- {
- // Create a new list.
- ListWithChangedEvent list = new ListWithChangedEvent();
- // Create a class that listens to the list's change event.
- EventListener listener = new EventListener(list);
- // Add and remove items from the list.
- list.Add("item 1");
- list.Clear();
- listener.Detach();
- }
- }
- }
- 输出:
- This is called when the eventfires.
- This is called when the eventfires.
代码讨论:
・ 声明事件。要在一个类的内部声明一个事件,首先必须声明一个委托(delegate)类型。
代码中声明如下:
public delegate void ChangedEventHandler(object sender,EventArgs e);
委托(delegate)类型定义了一个传递给处理事件的方法的参数集,多个事件能够共享相同的委托类型,因此确认是否有合适的委托类型被声明是必要的步骤。
接下来是事件自身的声明。
代码中声明如下:
public event ChangedEventHandler Changed;
事件的声明就像一个委托类型的字段,在事件声明的关键字event之前,要有访问修饰关键字。事件通常被声明为public类型,但是其他的任意可访问的修饰词也是被允许的。
・调用事件。一旦类声明了一个事件,那么它能够像处理委托类型的字段一样处理事件。如果没有使用者把委托连接到这个事件上,那么这个字段的值是null,否则这个字段会指向事件发生时应该被调用的委托,调用一个事件通常首先要检查这个字段的值是否是null,然后再调用这个事件。如下记代码。
if(Changed != null)
Changed(this, e)
注:只能从声明事件的类的内部来调用一个事件。
・关连事件(Hooking up to an event)。从声明事件的类的外部,一个事件看起来就像一个类的字段,但是访问这个字段是受到限制的,只有下面的事情可以做:
1、 把新的委托关联到这个字段
2、 从这个字段中把委托删除
关联和删除委托时,可以使用+=和-=操作符,要开始接受事件的请求,客户代码首先要创建事件类型委托所要引用的方法,这个方法应该由事件来调用,然后使用+=操作符把委托关联的事件字段上。如下面的代码。
// Add "ListChanged" to the Changed event on "List":
List.Changed += new ChangedEventHandler(ListChanged);
当客户代码完成了接收事件的请求时,可以使用-=操作符把委托从事件的关连中删除。如下记代码:
// Detach the event and delete the list:
List.Changed -= new ChangedEventHandler(ListChanged);
事件和继承(Events and Inheritance)
当创建一个通用的可以被继承的组件时,有些时候事件的继承似乎成为问题,尽管有些时候是希望给派生类适当的自由调用事件的能力,但是因为事件只能在声明它们的类的内部调用触发,派生类不能直接调用在基类中声明的事件。典型的可以通过在基类中创建protected类型的触发事件的方法来达到在派生类中调用的目的。为了使事件更灵活,触发事件的方法经常被声明成virtual类型的,允许派生类重写这个方法,这样就可以截获基类正在触发的事件,从而使得派生类能够在事件中做自己处理。
接口中的事件(Events in Interfaces)
事件和类字段之间的另一个不同点是,事件可以被放到接口中,而类字段不可以。当实现一个接口的时候,实现接口的类必须在实现类中提供相应的事件。
.Net Framework 标准(.NET Framework Guidelines)
尽管c#语言的事件允许使用任意的委托类型,但是.Net Framework对于事件所使用的委托类型有更严格的要求,如果打算把组件放到.Net Framework中使用,就需要了解下面的标准。
.Net Framework标准指出,用于事件的委托应该有两个参数,一个是”对象源(object source)”参数,用于指示事件的发生源(即事件是谁触发的),另一个是”e”参数,它可以封装任意其他的关于事件的信息,”e”类型的参数应该继承EventArgs类,对于不使用额外的其他信息的事件,.Net Framework已经定义了一个适当的委托类型:EventHandler。
例2
下面的例子是按照.Net Framework的标准对例1修正后的版本,这个例子中使用了EventHandler委托类型。
- EventHandler委托类型。
- // events2.cs
- using System;
- namespace MyCollections
- {
- using System.Collections;
- // A class that works just like ArrayList, but sends event
- // notifications whenever the list changes:
- public class ListWithChangedEvent: ArrayList
- {
- // An event that clients can use to be notified whenever the
- // elements of the list change:
- public event EventHandler Changed;
- // Invoke the Changed event; called whenever list changes:
- protected virtual void OnChanged(EventArgs e)
- {
- if (Changed != null)
- Changed(this,e);
- }
- // Override some of the methods that can change the list;
- // invoke event after each:
- public override int Add(object value)
- {
- int i = base.Add(value);
- OnChanged(EventArgs.Empty);
- return i;
- }
- public override void Clear()
- {
- base.Clear();
- OnChanged(EventArgs.Empty);
- }
- public override object this[int index]
- {
- set
- {
- base[index] = value;
- OnChanged(EventArgs.Empty);
- }
- }
- }
- }
- namespace TestEvents
- {
- using MyCollections;
- class EventListener
- {
- private ListWithChangedEvent List;
- public EventListener(ListWithChangedEvent list)
- {
- List = list;
- // Add "ListChanged" to the Changed event on "List":
- List.Changed += new EventHandler(ListChanged);
- }
- // This will be called whenever the list changes:
- private void ListChanged(object sender, EventArgs e)
- {
- Console.WriteLine("This is called when the event fires.");
- }
- public void Detach()
- {
- // Detach the event and delete the list:
- List.Changed -= new EventHandler(ListChanged);
- List = null;
- }
- }
- class Test
- {
- // Test the ListWithChangedEvent class:
- public static void Main()
- {
- // Create a new list:
- ListWithChangedEvent list = new ListWithChangedEvent();
- // Create a class that listens to the list's change event:
- EventListener listener = new EventListener(list);
- // Add and remove items from the list:
- list.Add("item 1");
- list.Clear();
- listener.Detach();
- }
- }
- }
- 输出:
- This is called when the event fires.
- This is called when the event fires.