1

我有一个包含许多类的类库,并且在每个类中我都会引发一些事件。

每个事件都有自己的一组事件参数,因此我将这些作为自动属性存储在继承自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如果不需要则忽略myEvent

问题是我在 MyClass 中有一个空方法。想象一下这种情况,但有大约 30 个事件,因此有 30 个空方法。

几个问题

  1. 我说得有道理吗?就像你至少理解我想要做什么一样,即使你认为我做这一切都错了?
  2. 这里实际上是否存在问题,或者这是实现我想要实现的功能的相当标准的方式?

谢谢

4

7 回答 7

4

一个事件 MyEvent 的典型实现如下:

protected virtual void OnMyEvent(MyEventArgs eventArgs) {
    var handler = MyEvent;
    if (handler != null) {
        handler(this, eventArgs);
    }
}

然后,只要您想触发事件,就调用 OnMyEvent 而不是 MyEvent。

于 2012-07-31T14:55:42.087 回答
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"));

如果没有订阅者,该事件将为空。此代码获取本地副本以确保检查点的事件不涉及竞争条件。然后调用该副本。尽管这以一种形式防止了竞争条件,但原始订阅者列表仍可能发生更改,这些更改在您的副本中不可见,因此它不是完全线程安全的。

老实说,我从来没有考虑过,甚至没有见过,以你的方式去做。我说最好坚持使用空检查而不是空方法订阅者,人们期望前者,而不是后者。

此外,空方法路由会消耗内存/对象,而空路由只需检查一次。

顺便说一句,MyEventHandlerarguments 类通常被称为MyEventArgs.

于 2012-07-31T14:54:01.030 回答
2

值得注意的是,虽然多播委托可用于具有任意数量订阅者的事件,但Delegate.Combine在结果等于传入委托之一Delegate.Remove的情况下优化了 的性能,针对结果为be null,并且Delegate.Invoke针对它只调用一个委托的情况进行了优化(并且OnMyEvent在没有委托的情况下执行空检查会更快)。因此,添加一个空的事件处理程序会使代码的效率低于其他情况。

一种尚未提及的方法是创建一个静态的无操作委托,然后根据需要更改添加/删除处理程序:添加订阅时,如果旧订阅列表委托与静态无操作委托匹配,则Interlocked.CompareExchange使用存储新的代表;否则计算Delegate.Combine构建一个新的委托和CompareExchange那个(在任何一种情况下,如果CompareExchange失败,重试添加订阅方法)。删除订阅时,用于Delegate.Remove计算新列表;如果为 null,则替换静态无操作委托。然后用于CompareExchange更新订阅列表委托。

这种方法会稍微减慢AddandRemove方法,尽管不如简单地将无操作委托留在列表中;零订阅者情况下的委托调用会比空检查稍慢,但单订阅者情况下会稍微快一些。

于 2012-12-10T16:35:33.057 回答
1

处理此问题的规范方法是不引发没有订阅者的事件:

var handler = myEvent;
if (handler != null)
{
    handler(sender, new MyEventArgs());
}

handler如果在多线程场景中单个订阅者在null检查和调用之间取消订阅,则分配给中间变量可以避免出现异常。

于 2012-07-31T14:55:02.623 回答
0

使用空处理程序初始化事件没有任何问题。我一直都这样做。如果您调用该事件数千次,它可能会比您想要的慢(测试、测量、决定)......

设置空处理程序时不必那么罗嗦,您可以这样做:

myEvent = (sender, args) => { };

并避免创建方法...

于 2012-07-31T14:57:03.533 回答
0

null通常的做法是在引发事件之前进行检查。在你的情况下:

if (myEvent != null)
  myEvent.Invoke(this, new MyEventHandler("The quick brown fox jumps over the lazy dog")); 
于 2012-07-31T14:53:28.267 回答
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
    }
于 2012-07-31T14:57:11.620 回答