C# 事件

event 事件
定义[修饰符] event void <委托类型> [接口类型.] <事件名称>;

event 关键字用于指定事件。类和结构使用事件将出现的可能影响对象状态的事件通知给对象。

向类中添加事件需要使用 event 关键字,并提供委托类型和事件名称。

事件使用委托来为触发时将调用的方法提供类型安全的封装。委托可以封装命名方法匿名方法

在下面的示例中,类 TestButton 包含事件 OnClick。派生自 TestButton 的类可以选择响应 OnClick 事件,并且定义了处理事件要调用的方法。可以以委托和匿名方法的形式指定多个处理程序。

// Declare the handler delegate for the event
public delegate void ButtonEventHandler();  

class TestButton
{
   
// OnClick is an event, implemented by a delegate ButtonEventHandler.
    public event ButtonEventHandler OnClick;

   
// A method that triggers the event:
    public void Click()
    {
        OnClick();
    }
}
// Create an instance of the TestButton class.
TestButton mb = new TestButton();

// Specify the method that will be triggered by the OnClick event.
mb.OnClick += new ButtonEventHandler(TestHandler);

// Specify an additional anonymous method.
mb.OnClick += delegate { System.Console.WriteLine("Hello, World!"); };

// Trigger the event
mb.Click();

事件概述

事件具有以下特点:

  • 事件是类用来通知对象需要执行某种操作的方式。

  • 尽管事件在其他时候(如信号状态更改)也很有用,事件通常还是用在图形用户界面中。

  • 事件通常使用委托事件处理程序进行声明。

  • 事件可以调用匿名方法来替代委托。有关更多信息,请参见匿名方法

 

引发事件

若要引发事件,类可以调用委托,并传递所有与事件有关的参数。然后,委托调用已添加到该事件的所有处理程序。如果该事件没有任何处理程序,则该事件为空。因此在引发事件之前,事件源应确保该事件不为空以避免 NullReferenceException。若要避免争用条件(最后一个处理程序会在空检查和事件调用之间被移除),在执行空检查和引发事件之前,事件源还应创建事件的一个副本。例如:

private void RaiseTestEvent()
{
    // Safely invoke an event:
    TestEventDelegate temp = TestEvent;

    if (temp != null)
    {
        temp(this, new System.EventArgs());
    }
}

订阅事件

要接收某个事件的类可以创建一个方法来接收该事件,然后向类事件自身添加该方法的一个委托。这个过程称为“订阅事件”。

首先,接收类必须具有与事件自身具有相同签名(如委托签名)的方法。然后,该方法(称为事件处理程序)可以采取适当的操作来响应该事件。例如

public class EventReceiver
{
    public void ReceiveTestEvent(object sender, System.EventArgs e)
    {
        System.Console.Write("Event received from ");
        System.Console.WriteLine(sender.ToString());
    }
}

每个事件可有多个处理程序。多个处理程序由源按顺序调用。如果一个处理程序引发异常,还未调用的处理程序则没有机会接收事件。由于这个原因,建议事件处理程序迅速处理事件并避免引发异常。

若要订阅事件,接收器必须创建一个与事件具有相同类型的委托,并使用事件处理程序作为委托目标。然后,接收器必须使用加法赋值运算符 (+=) 将该委托添加到源对象的事件中。例如:

public void Subscribe(EventSource source)
{
    TestEventDelegate temp = new TestEventDelegate(ReceiveTestEvent);
    source.TestEvent += temp;
}

若要取消订阅事件,接收器可以使用减法赋值运算符 (-=) 从源对象的事件中移除事件处理程序的委托。例如:

public void UnSubscribe(EventSource source)
{
    TestEventDelegate temp = new TestEventDelegate(ReceiveTestEvent);
    source.TestEvent -= temp;
}
声明事件访问器
使用 add 关键字和代码块添加事件的事件处理程序,使用 remove 关键字和代码块移除事件的事件处理程序。
public class EventSource2
{
    private TestEventDelegate TestEventHandlers;
    public event TestEventDelegate TestEvent
    {
        add
        {
            lock (TestEventHandlers)
            {
                TestEventHandlers += value;
            }
        }
        remove
        {
            lock (TestEventHandlers)
            {
                TestEventHandlers -= value;
            }
        }
    }
    private void RaiseTestEvent()
    {
        // Safely invoke an event.
        TestEventDelegate temp = TestEventHandlers;

        if (temp != null)
        {
            temp(this, new System.EventArgs());
        }
    }
}

前面示例中的 lock 语句用于防止多个线程同时操作事件列表。请参见 lock 语句和线程处理。

创建响应事件的控件

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

ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_csref/html/2d20556a-0aad-46fc-845e-f85d86ea617a.htm

示例:

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();
        }
    }
}
.NET Framework 指南指示用于事件的委托类型应采用两个参数:“对象源”参数(用于指示事件源)和特定于事件的参数(它封装有关事件的其他任何信息)。特定于事件的参数应从 EventArgs 类派生。对于不使用任何附加信息的事件,.NET Framework 提供了 EventHandler 类
如上面例子中修改成 .Net Framework 规则
namespace TestCollections
{
    // 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 System.EventHandler 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);
            }
        }
    }
}
在接口中声明一个事件,然后在类中实现该事件
public delegate void TestDelegate();   // delegate declaration

public interface ITestInterface
{
    event TestDelegate TestEvent;
    void FireAway();
}

public class TestClass : ITestInterface
{
    public event TestDelegate TestEvent;

    public void FireAway()
    {
        if (TestEvent != null)
        {
            TestEvent();
        }
    }
}

public class MainClass
{
    static private void F()
    {
        System.Console.WriteLine("This is called when the event fires.");
    }

    static void Main()
    {
        ITestInterface i = new TestClass();

        i.TestEvent += new TestDelegate(F);
        i.FireAway();
    }
}
实现两个具有同名事件的接口

当要实现两个具有同名事件的接口时,也需要用到事件属性。在这种情况下,必须使用显式实现事件属性

但是,在显式地实现接口中的事件时,您需要提供添加和移除方法。

示例:ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_csref/html/47f2d6d6-6d3c-4835-8483-fb48ff52f228.htm

posted @ 2009-08-11 22:47  幽灵湖  阅读(312)  评论(0编辑  收藏  举报