正如克里斯格雷所说,一个用途是发出你的代码没有直接调用的事情.这里最常见的原因可能是GUI上的用户操作.另一个例子可能是在另一个线程上完成的异步操作.
使用事件的另一个原因是,当您不知道谁可能对刚刚发生的事情感兴趣时.提升事件的类不需要知道(在设计时)任何关于其他类可能感兴趣的实例的数量.
class Raiser {
public DoSomething() {
//Do something long winded.
OnDidSomething(new DidSomethingEventArgs());
}
public EventHandler<DidSomethingEventArgs> DidSomething;
private OnDidSomething(DidSomethingEventArgs e) {
if (DidSomething != null)
DidSomething(this, e);
}
}
Run Code Online (Sandbox Code Playgroud)
显然,您还需要定义DidSomethingEventArgs类,该类传递有关事件的相关数据.这也说明了事件的常见命名约定.如果事件被称为X,则事件仅在名为OnX的方法中引发,并且它传递的任何数据都是类XEventArgs的实例.请注意,如果没有订阅侦听器,则事件可以为null,因此在我们引发事件之前进行检查.
请注意,这个类对其他类可能对它做了什么感兴趣的事情一无所知.它只是宣布它已经做到了这一事实.
然后,多个类可以监听事件:
class ListenerA {
private Raiser r;
ListenerA(Raiser r) {
this.r = r;
r.DidSomething += R_DidSomething;
}
R_DidSomething(object sender, DidSomethingEventArgs e) {
//Do something with the result.
}
}
Run Code Online (Sandbox Code Playgroud)
和:
class ListenerB {
private Raiser r;
ListenerB(Raiser r) {
this.r = r;
r.DidSomething += R_DidSomething;
}
R_DidSomething(object sender, DidSomethingEventArgs e) {
//Do something with the result.
}
}
Run Code Online (Sandbox Code Playgroud)
现在,当在Raiser实例上调用DoSomething方法时,将通过DidSomething事件通知ListenerA和ListenerB的所有实例.请注意,侦听器类可以很容易地与提升者在不同的程序集中.他们需要一个引用回到raiser的程序集,但它不需要引用它的监听器程序集.
请注意,上述简单的Raiser示例可能会在多线程程序中导致一些问题.更强大的示例将使用如下内容:
class Raiser {
public DoSomething() {
//Do something long winded.
OnDidSomething(new DidSomethingEventArgs());
}
#region DidSomething Event
private object _DidSomethingLock = new object();
private EventHandler<DidSomethingEventArgs> _DidSomething;
public EventHandler<DidSomethingEventArgs> DidSomething {
add { lock(_DidSomethinglock) _DidSomething += value; }
remove { lock(_DidSomethinglock) _DidSomething -= value; }
}
OnDidSomething(DidSomethingEventArgs e) {
EventHandler<DidSomethingEventArgs> handler;
lock (_DidSomethingLock)
handler = _DidSomething;
if (handler == null)
return;
try {
DidSomething(this, e);
} catch (Exception ex) {
//Do something with the exception
}
}
#endregion
}
Run Code Online (Sandbox Code Playgroud)
这可确保在您处于引发事件的过程中添加或删除侦听器的另一个线程不会导致问题.
如果正在创建和销毁侦听器类的实例,则此处使用的简单侦听器也将导致内存泄漏.这是因为Raiser实例在订阅事件时会传递(并存储)对每个侦听器的引用.这足以防止垃圾收集器在删除所有对它们的显式引用时正确整理侦听器.解决此问题的最佳方法可能是使侦听器实现IDisposable接口并取消订阅Dispose方法中的事件.然后你只需要记住调用Dispose方法.