使用扩展方法提升C#事件 - 这很糟糕吗?

Rya*_*ndy 50 .net c# events extension-methods event-handling

我们都熟悉C#事件声明的恐怖.为了确保线程安全,标准是写这样的东西:

public event EventHandler SomethingHappened;
protected virtual void OnSomethingHappened(EventArgs e)
{            
    var handler = SomethingHappened;
    if (handler != null)
        handler(this, e);
}
Run Code Online (Sandbox Code Playgroud)

最近在这个板上的一些其他问题(我现在找不到),有人指出在这种情况下可以很好地使用扩展方法.这是一种方法:

static public class EventExtensions
{
    static public void RaiseEvent(this EventHandler @event, object sender, EventArgs e)
    {
        var handler = @event;
        if (handler != null)
            handler(sender, e);
    }
    static public void RaiseEvent<T>(this EventHandler<T> @event, object sender, T e)
        where T : EventArgs
    {
        var handler = @event;
        if (handler != null)
            handler(sender, e);
    }
}
Run Code Online (Sandbox Code Playgroud)

有了这些扩展方法,你需要声明和引发一个事件就像这样:

public event EventHandler SomethingHappened;

void SomeMethod()
{
    this.SomethingHappened.RaiseEvent(this, EventArgs.Empty);
}
Run Code Online (Sandbox Code Playgroud)

我的问题:这是个好主意吗?没有标准的On方法,我们是否遗漏了什么?(我注意到的一件事是它不适用于具有显式添加/删除代码的事件.)

Jon*_*eet 56

它仍然适用于具有显式添加/删除的事件 - 您只需要使用委托变量(或者您已经存储了委托)而不是事件名称.

但是,有一种更简单的方法可以使其成为线程安全的 - 使用no-op处理程序初始化它:

public event EventHandler SomethingHappened = delegate {};
Run Code Online (Sandbox Code Playgroud)

调用额外委托的性能可以忽略不计,它确实使代码更容易.

顺便说一句,在您的扩展方法中,您不需要额外的局部变量 - 您可以这样做:

static public void RaiseEvent(this EventHandler @event, object sender, EventArgs e)
{
    if (@event != null)
        @event(sender, e);
}

static public void RaiseEvent<T>(this EventHandler<T> @event, object sender, T e)
    where T : EventArgs
{
    if (@event != null)
        @event(sender, e);
}
Run Code Online (Sandbox Code Playgroud)

我个人不会使用关键字作为参数名称,但它根本没有真正改变调用方面,所以做你想要的:)

编辑:至于"OnXXX"方法:你是否计划派生你的课程?在我看来,大多数课程都应该密封.如果这样做,您是否希望这些派生类能够引发事件?如果这些问题中的任何一个的答案都是"不",那就不要打扰了.如果两者的答案都是"是",那么:)

  • @Mike:不,我不相信JITter*被允许这样做.这是一段时间以前提出的问题,但我已经和某人说过了(我不记得是Joe Duffy还是Vance Morrison;有人这样),他说这完全违反了JITter所允许的规则和记忆模型. (5认同)
  • 你们需要将[MethodImpl(MethodImplOptions.NoInlining)]属性添加到这些扩展方法中,否则你可以通过JITter优化将代理复制到临时变量的尝试,从而允许空引用异常.这可能发生在Kyralessa和Jon Skeet的版本中.所以只需添加[MethodImpl(MethodImplOptions.NoInlining)]就可以了,是的,您不再需要显式复制到临时变量,因为只需通过方法参数传入值就可以实现这一点. (3认同)
  • @Ghopper21:不,在这种情况下,参数充当局部变量,实际上 - 事件处理程序的任何机会都不会在`RaiseEvent`中可见,因为`@ event`只是一个参数...一个正常的局部变量.调用该方法时,该字段的当前值将被复制为参数的值...并且对该字段的进一步更改不会影响它. (2认同)

Bob*_*ers 13

现在C#6在这里,有一种更紧凑,线程安全的方式来触发事件:

SomethingHappened?.Invoke(this, e);
Run Code Online (Sandbox Code Playgroud)

Invoke() 只有委托者为事件注册时才会被调用(即它不为空),这要归功于空条件运算符"?".

问题所解决的"处理程序"代码的线程问题在这里被回避,因为,就像在该代码中一样,SomethingHappened只访问一次,因此在测试和调用之间不可能将其设置为null.

这个答案可能与原始问题相关,但对那些寻找更简单方法来提高事件的人来说非常相关.


Rob*_*son 5

[这是一个想法]

只需以推荐的方式编写代码一次并完成它.那么你不会混淆你的同事看着代码,认为你做错了什么?

[我阅读了更多关于编写事件处理程序的方法,而不是编写事件处理程序.]