基础_C# 事件

来源:互联网 发布:英语家教 知乎 编辑:程序博客网 时间:2024/06/06 00:53

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 declarationpublic 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();    }}
实现两个具有同名事件的接口

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

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

原创粉丝点击