C# 事件
event 关键字用于指定事件。类和结构使用事件将出现的可能影响对象状态的事件通知给对象。
向类中添加事件需要使用 event 关键字,并提供委托类型和事件名称。
在下面的示例中,类 TestButton
包含事件 OnClick。派生自 TestButton
的类可以选择响应 OnClick 事件,并且定义了处理事件要调用的方法。可以以委托和匿名方法的形式指定多个处理程序。
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();
}
}
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 语句和线程处理。
创建响应事件的控件
案例:某字处理器可能包含打开的文档的列表。每当该列表更改时,可能需要通知字处理器中的许多不同对象,以便能够更新用户界面。使用事件,维护文档列表的代码不需要知道需要通知谁,一旦文档列表发生了更改,将自动调用该事件,正确通知每个需要通知的对象。使用事件提高了程序的模块化程度。
示例:
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();
}
}
实现两个具有同名事件的接口
当要实现两个具有同名事件的接口时,也需要用到事件属性。在这种情况下,必须使用显式实现事件属性。
但是,在显式地实现接口中的事件时,您需要提供添加和移除方法。