我有一个包含许多类的类库,在每个类中我都会引发一些事件。
每个事件都有自己的一组事件参数,因此我将这些参数作为自动属性存储在继承自EventArgs
的类中。然后,我可以通过简单地调用Invoke
并传入我的EventArgs
继承类的新实例来引发相关事件。这就是我的意思:
using System;
//My Class Library
namespace MyClassLibrary
{
//A class
public class MyClass
{
//My event is a field
public event EventHandler<MyEventHandler> myEvent;
//I raise my event in this method
public void InvokeMyEvent()
{
//Do some stuff
//I raise my event here
myEvent.Invoke(this, new MyEventHandler("The quick brown fox jumps over the lazy dog"));
//Do some more stuff
}
}
//An event handler, containing some interesting data about the event
public class MyEventHandler : EventArgs
{
//Some interesting data as an automatic property
public string MyInterestingData { get; private set; }
//I assign the value of my intersting data in my constructor
public MyEventHandler(string FooBar)
{
MyInterestingData = FooBar;
}
}
}
所以这就是我想要做的。现在,在我的应用程序中,我可以添加对类库的引用,实例化MyClass
并订阅我的事件,就像这样。关于这一点的好处(使用通用事件处理程序)是Intellisense允许我在订阅我的事件时使用Tab键,并为我设置方法,默认情况下传入MyEventHandler。没有必要的铸造。
using System;
using MyClassLibrary;
namespace ConsoleApplication33
{
class Program
{
static void Main(string[] args)
{
MyClass x = new MyClass();
x.myEvent += new EventHandler<MyEventHandler>(x_myEvent);
}
static void x_myEvent(object sender, MyEventHandler e)
{
//Doing lots of important stuff
//Able to access my interesting data in my event handler without casting
var y = e.MyInterestingData;
}
}
}
这一切都很好,但是类库的要点肯定是有一堆可重用的代码,这当然是这里的意图。所以我想将我的类库添加到许多项目中。在其中一些项目中,我需要订阅myEvent
,在其他项目中它不会,但我仍然希望在这些项目中使用该类的其他功能,并且可以选择订阅将来myEvent
。
但是,如果我在我没有订阅myEvent
的项目中使用我的类库,那么每当myEvent
被引发时我都会收到运行时错误。
我通过在myEvent
的构造函数中使用一个空方法订阅MyClass
来解决这个问题:
public MyClass()
{
myEvent += new EventHandler<MyEventHandler>(MyClass_myEvent);
}
void MyClass_myEvent(object sender, MyEventHandler e)
{
}
这意味着我可以将我的类库添加到任何项目中,实例化MyClass
并使用它提供的其他功能,并在需要时订阅myEvent
,同时忽略{{1}如果我不这样做。
这个问题是我在MyClass中有一个空方法。想象一下这个场景,但有大约30个事件,因此有30个空方法。
几个问题
由于
答案 0 :(得分:4)
事件MyEvent的典型实现如下:
protected virtual void OnMyEvent(MyEventArgs eventArgs) {
var handler = MyEvent;
if (handler != null) {
handler(this, eventArgs);
}
}
然后,只要您想触发事件,就可以调用OnMyEvent而不是MyEvent。
答案 1 :(得分:3)
这通常是您看到以下带有事件的模式的原因:
private void OnMyEvent(object sender, MyEventArgs args)
{
var ev = myEvent;
if (ev != null)
ev(sender, args);
}
OnMyEvent(this, new MyEventArgs("The quick brown fox jumps over the lazy dog"));
如果没有订阅者,该事件将为null。此代码采用本地副本以确保检查点的事件不参与竞争条件。然后调用该副本。即使这样可以防止一种形式的竞争条件,原始订户列表仍然可以进行更改,这些更改在您的副本中不可见,因此它不完全是线程安全的。
说实话,我从未考虑过,甚至没有看到过这样做的方式。我说最好坚持使用空检查而不是空方法订阅者,人们期待前者,而不是后者。
此外,空方法路由会花费内存/对象,其中空路由只需要检查。
除此之外,MyEventHandler
参数类通常称为MyEventArgs
。
答案 2 :(得分:2)
值得注意的是,虽然多播委托可以用于具有任意数量订阅者的事件,但Delegate.Combine
的性能针对结果将等于传入的委托之一,Delegate.Remove
的性能进行了优化。 {1}}针对结果为null
的情况进行了优化,Delegate.Invoke
针对仅调用一个委托(以及进行空检查的OnMyEvent
的情况进行了优化在没有代表的情况下会更快。)因此,添加空事件处理程序将使代码的效率低于其他情况。
尚未提及的方法是创建静态do-nothing委托,然后根据需要更改一个添加/删除处理程序:添加订阅时,如果旧的subscription-list委托与静态do-nothing委托匹配,使用Interlocked.CompareExchange
来存储新代表;否则计算Delegate.Combine
以构建新的委托和CompareExchange
(在任何一种情况下,如果CompareExchange
失败,则重试add-subscription方法)。删除订阅时,请使用Delegate.Remove
来确定新列表;如果为null,则替换静态do-nothing委托。然后使用CompareExchange
更新订阅列表委托。
这种方法会稍微降低Add
和Remove
方法的速度,但不会像在列表中留下do-nothing委托一样多;零订户情况下的委托调用将比空检查略慢,但单订户情况会稍快一些。
答案 3 :(得分:1)
处理此问题的规范方法是不引发没有订阅者的事件:
var handler = myEvent;
if (handler != null)
{
handler(sender, new MyEventArgs());
}
如果在多线程场景中,单个订阅者在handler
检查和调用之间取消订阅,则分配给中间null
变量可避免出现异常。
答案 4 :(得分:0)
通常的做法是在举起活动前检查null
。在你的情况下:
if (myEvent != null)
myEvent.Invoke(this, new MyEventHandler("The quick brown fox jumps over the lazy dog"));
答案 5 :(得分:0)
使用空处理程序初始化事件没有任何问题。我一直这样做。如果您将该事件召唤数千次,则可能比您想要的慢(测试,测量,决定)......
设置空处理程序时,您不必如此罗嗦,您可以这样做:
myEvent = (sender, args) => { };
并避免创建方法...
答案 6 :(得分:0)
我希望我能看到你的成就。我想,你可以为自己的空方法省去工作。只需按以下方式调用您的活动:
//I raise my event in this method
public void InvokeMyEvent()
{
//Do some stuff
//Check if there are subscribers!!
if (myEvent != null)
myEvent.Invoke(this, new MyEventHandler("The quick brown fox jumps over the lazy dog"));
//Do some more stuff
}