下面的简单示例演示ListWithChangedEvent,它类似于标准的 ArrayList 类,此外,每当列表的内容更改时,它还调用 Changed事件。这样一个通用用途的类可在大型程序中以多种方式使用。

例如,某字处理器可能包含打开的文档的列表。每当该列表更改时,可能需要通知字处理器中的许多不同对象,以便能够更新用户界面。使用事件,维护文档列表的代码不需要知道需要通知谁,一旦文档列表发生了更改,将自动调用该事件,正确通知每个需要通知的对象。使用事件提高了程序的模块化程度。

在创建可用作其他组件的基类的通用组件时,必须考虑如下事实,即事件(与字段不同)只能从声明它们的类中调用。派生类不能直接调用基类中声明的事件。虽然这有时符合需要,但通常使派生类能够自由调用事件更合适。这通常通过为事件创建受保护的调用方法来实现。通过调用该调用方法,派生类便可以调用此事件。为获得更大的灵活性,调用方法通常声明为虚拟的,这允许派生类重写调用方法。这使得派生类可以截获基类正在调用的事件,有可能对这些事件执行它自己的处理。

在下面的示例中,这已用 OnChanged 方法实现。如果需要,派生类可调用或重写该方法。

事件和字段之间的另一个差异是,事件可放在接口中,而字段不能。当实现接口时,实现类必须在实现接口的类中提供相应的事件。

示例

C# CopyCode image复制代码
namespace TestCollections
{
    // A delegate type for hooking up change notifications.
    public delegate void ChangedEventHandler(object sender, System.EventArgs e);

    // A class that works just like ArrayList, but sends event
    // notifications whenever the list changes.
    public class ListWithChangedEvent : System.Collections.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(System.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(System.EventArgs.Empty);
            return i;
        }

        public override void Clear()
        {
            base.Clear();
            OnChanged(System.EventArgs.Empty);
        }

        public override object this[int index]
        {
            set
            {
                base[index] = value;
                OnChanged(System.EventArgs.Empty);
            }
        }
    }
}

namespace TestEvents
{
    using TestCollections;

    class EventListener
    {
        private ListWithChangedEvent m_list;

        public EventListener(ListWithChangedEvent list)
        {
            m_list = list;

            // Add "ListChanged" to the Changed event on m_list:
            m_list.Changed += new ChangedEventHandler(ListChanged);
        }

        // This will be called whenever the list changes.
        private void ListChanged(object sender, System.EventArgs e)
        {
            System.Console.WriteLine("This is called when the event fires.");
        }

        public void Detach()
        {
            // Detach the event and delete the list
            m_list.Changed -= new ChangedEventHandler(ListChanged);
            m_list = null;
        }
    }

    class Test
    {
        // Test the ListWithChangedEvent class.
        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.

可靠编程

  • 声明事件

    若要在类中声明事件,首先必须声明该事件的委托类型(如果尚未声明的话)。

    C# CopyCode image复制代码
    public delegate void ChangedEventHandler(object sender, System.EventArgs e);
    

    委托类型定义传递给处理该事件的方法的一组参数。多个事件可共享相同的委托类型,因此仅当尚未声明任何合适的委托类型时才需要执行该步骤。

    接下来,声明事件本身。

    C# CopyCode image复制代码
    public event ChangedEventHandler Changed;
    

    声明事件的方法与声明委托类型的字段类似,区别在于关键字 event 在事件声明的前面、修饰符的后面。事件通常被声明为公共事件,但允许使用任何可访问性修饰符。

  • 调用事件

    类声明了事件以后,可以就像处理所指示的委托类型的字段那样处理该事件。如果没有任何客户将委托与该事件挂钩,该字段将为空;否则该字段引用应在调用该事件时调用的委托。因此,调用事件时通常先检查是否为空,然后再调用事件。

    C# CopyCode image复制代码
    if (Changed != null)
    {
        Changed(this, e);
    }
    

    调用事件只能从声明该事件的类内进行。

  • 挂钩到事件

    从声明事件的类的外部来看,事件就像字段,只不过对该字段的访问受到严格的限制。唯一能做的事情就是将一个新的委托组合到字段上,以及从字段(或许是组合的)移除一个委托。

    使用 +=-= 运算符完成此操作。为开始接收事件调用,客户代码先创建事件类型的委托,该委托引用应从事件调用的方法。然后它使用 += 将该委托写到事件可能连接到的其他任何委托上。

    C# CopyCode image复制代码
    m_list.Changed += new ChangedEventHandler(ListChanged);
    

    当客户代码完成接收事件调用后,它将使用运算符 -= 从事件移除其委托。

    C# CopyCode image复制代码
    m_list.Changed -= new ChangedEventHandler(ListChanged);
    

请参见