在“null”引用(即没有订阅者的事件)上调用扩展方法是否邪恶?

58

邪恶还是不邪恶?

public static void Raise(this EventHandler handler, object sender, EventArgs args)
{
   if (handler != null)
   {
      handler(sender, args);
   }
}

// Usage:
MyButtonClicked.Raise(this, EventArgs.Empty);

// This works too! Evil?
EventHandler handler = null;
handler.Raise(this, EVentArgs.Empty);

请注意,由于扩展方法的特性,如果MyButtonClicked为null(例如,没有MyButtonClicked事件的监听器),MyButtonClicked.Raise不会抛出NullReferenceException异常。

邪恶还是无害?


8
这很有用。我们可以用一行代码代替在代码库中到处散布的数百个"if (SomeEvent != null) SomeEvent(this, args)"语句。请注意,我的翻译保持了原意并使其更加通俗易懂,同时没有添加解释或其他额外的信息。 - Judah Gabriel Himango
或者,您可以为事件创建一个“虚拟处理程序”,以确保它永远不为空。 - Joel Coehoorn
Joel - 虚拟处理程序的性能影响是什么?虽然我还没有证明,但我愿意打赌条件检查比委托调用要便宜。 - Erik Forbes
如果将其内联,则可以解决线程安全问题,否则如果不使用类似此类的东西,则需要另一行样板代码。 - Mark Sowul
这是一个老问题,但根据当前的“规则”,它应该被关闭。尽管如此,仍然很有趣可读。 - JDB
显示剩余4条评论
8个回答

35

并不是出了岔子。我希望事件默认情况下能够这样工作。有没有人能解释一下,为什么一个没有订阅者的事件是 null?


2
我同意没有订阅者的事件不应该为null。一个没有订阅者的事件为null似乎相当愚蠢 - 当然他们可以使用某个可重用类来初始化它,其中包含一个空的订阅列表。唉,现在改变已经太晚了。 - Judah Gabriel Himango
我完全同意,并且有一个重载,将args参数默认为EventArgs.Empty。 - kͩeͣmͮpͥ ͩ
1
拥有没有订阅者的事件是可以为空的,这没问题。问题在于尝试调用一个返回类型为空的空事件会抛出异常。调用空委托应该会抛出异常,但是声明为“事件”的字段在这种情况下不应被视为委托。 - supercat

14

您总是可以这样声明您的事件(虽然我不推荐这样做):

public event EventHandler<EventArgs> OnClicked = delegate { };

当这样做时,它们在被调用时已经有了被分配的内容,因此不会抛出空指针异常。

在C# 3.0中,你可能可以摆脱委托关键字...


我推荐它。 它遵循良好的公民原则。 - Rinat Abdullin

9

6

作为一个Java背景的人,这对我来说一直很奇怪。我认为没有任何人监听事件是完全有效的。特别是当侦听器被动态添加和删除时。

对我来说,这似乎是C#中的一个陷阱,当人们不知道/忘记每次检查null时,会导致错误。

隐藏此实现细节似乎是一个好计划,因为每次检查null并不有助于可读性。我相信微软公司会说如果没有人在听,则不构建事件会有性能提升,但在大多数业务代码中,无意义的空指针异常/可读性降低远远超过了性能提升。

我还会向类中添加这两个方法:

    public static void Raise(this EventHandler handler, object sender)
    {
        Raise(handler, sender, EventArgs.Empty);
    }

    public static void Raise<TA>(this EventHandler<TA> handler, object sender, TA args)
        where TA : EventArgs
    {
        if (handler != null)
        {
            handler(sender, args);
        }
    }

5

为什么会被认为是邪恶的?

它的目的很明确:它会触发MyButtonClicked事件。

它确实会增加函数调用开销,但在.NET中它要么被优化掉,要么就非常快。

虽然有点琐碎,但它解决了我对C#的最大抱怨。

总的来说,我认为这是一个绝妙的想法,可能会被借鉴。


它之所以 “邪恶” 是因为如果 MyButtonClick 为空,它不会抛出异常。例如:这是有效的:EventHandler click = null; click.Raise(...); - Judah Gabriel Himango
你不能将 EventHandler 设置为 null。它唯一可能为 null 的方式是没有任何人监听该事件,而我认为触发一个没有任何人监听的事件并不值得引发异常。 - David
对的,我的意思是,如果没有订阅者参与事件,它将为null。然后,在可能为null的对象上调用类似实例方法的东西可能看起来像是对扩展方法的恶意使用。 - Judah Gabriel Himango

0

虽然我不会称它为邪恶,但仍然具有负面含义,因为它增加了不必要的开销:

在调用时

myEvent.Raise(this, new EventArgs());

无论是否有人订阅myEvent,都会在所有情况下初始化对象EventArgs。

在使用时

if (myEvent!= null) {
   myEvent(this, new EventArgs());
}

只有当有人订阅了myEvent时,EventArgs才会被初始化。


2
没错,我愿意为更易读、更健壮的代码牺牲一点内存分配。 - Judah Gabriel Himango
2
开销很小,如果你看看所有核心库的实现方式,它们都采用以下模式:public void SomeMethod() { OnSomeEvent(new SomeEventArgs()); }protected virtual void OnSomeEvent(SomeEventArgs e) { if (SomeEvent != null) { SomeEvent(this, e); } } - Bronumski

0

我不会说它是邪恶的,但我对你的扩展方法如何与之配合感兴趣

protected virtual OnSomeEvent(EventArgs e){ }

模式及其通过继承处理可扩展性的方式。它是否假定所有子类都将处理事件而不是覆盖方法?


不应该影响这个。在OnSomeEvent内部,您将使用扩展方法而不是正常的“检查null,如果不为null则引发”舞蹈。 - Judah Gabriel Himango

-2
在没有处理程序时抛出异常并不是大多数人首选的方式。如果它没有处理程序,最好为空而不是 null。

@MattEllen 噢,我以为这会有助于了解一般的做法。考虑到程序员的舒适度。无论如何还是谢谢。 - Prakash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接