为什么委托不处理事件null

时间:2010-10-19 07:49:26

标签: c# delegates null

调用委托时,您必须检查它是否为空。这通常是导致错误的原因。 由于委托或多或少只是一个函数列表,我认为这可以由委托本身轻松检查。

有谁知道,为什么它已经实现了?

7 个答案:

答案 0 :(得分:6)

这可能是明显的,但你可以声明你的事件指向一个no-op处理程序,然后你不需要在调用时检查null。

public event EventHandler MyEvent = delegate { };

然后,您可以调用MyEvent指向的处理程序,而不检查null。

答案 1 :(得分:4)

委托本身无法检查。由于它为null,因此无法在其上调用方法。

另一方面,C#编译器可以自动插入空检查。但是,如果委托是具有返回值的函数,我不知道要使用什么行为。

有人可能会争辩说,C#编译器应插入void函数的检查,以避免在常见情况下使用样板代码。但是这个决定现在已经过去了,如果你有一台时间机器,你只能在不破坏现有程序的情况下修复它。

您可以使用扩展方法(因为它们可以在空对象上调用)来自动执行事件的空值检查:
http://blogs.microsoft.co.il/blogs/shayf/archive/2009/05/25/a-handy-extension-method-raise-events-safely.aspx
由于参数是临时变量,因此您无需手动将事件分配给临时变量以实现线程安全。

答案 2 :(得分:3)

在内部,编译器将为类中声明的每个事件生成2个方法add_MyEvent和remove_MyEvent。当您编写MyEvent + = ...时,编译器实际上会生成对add_MyEvent的调用,而add_MyEvent又会调用System.Delegate.Combine。

Combine在参数中接受2个委托,并从2个原始委托创建一个新的(多播)委托,处理其中一个为空时的情况(这是第一次调用+ =的情况)。

我想编译器本来可以更智能一点,并且还处理了事件调用,以便在您调用时 MyEvent(),它实际上会生成一个空检查,并且只有在不为null时才会实际调用该委托。 (让Eric Lippert看到那个人会很高兴。)

答案 3 :(得分:2)

原因是性能(实际上,这是我最好的猜测)。事件和委托是由许多编译器魔法制作的,这些东西不能仅仅通过C#代码复制。但在它下面是这样的:

委托是一个编译器生成的类,它继承自MulticastDelegate类,它本身派生自Delegate类。注意这两个类是神奇的,你不能自己继承(好吧,也许你可以,但你不能很好地使用它们。)

然而事件的实现方式如下:

private MyEventDelegateClass __cgbe_MyEvent; // Compiler generated backing field

public event MyEventDelegateClass MyEvent
{
    add
    {
        this.__cgbe_MyEvent = (MyEventDelegateClass)Delegate.Combine(this.__cgbe_MyEvent, value);
    }
    remove
    {
        this.__cgbe_MyEvent = (MyEventDelegateClass)Delegate.Remove(this.__cgbe_MyEvent, value);
    }
    get
    {
        return this.__cgbe_MyEvent;
    }
}

好的,所以这不是真正的代码(也不是它在现实生活中的方式),但它应该让你知道发生了什么。

重点是支持字段最初确实为空。这样,在创建对象时创建MyEventDelegateClass的实例没有任何开销。这就是为什么你必须在调用之前检查null。稍后,当添加/删除处理程序时,会创建MyEventDelegateClass的实例并将其分配给该字段。当删除最后一个处理程序时,此实例也会丢失,并且支持字段将再次重置为null。

这是“你不为你不使用的东西买单”的原则。只要您不使用该事件,就不会有任何开销。没有额外的内存,没有额外的CPU周期。

答案 4 :(得分:1)

如果委托实例是null,它怎么能“自我检查”?它是null ......这意味着它基本上不存在。首先,没有任何东西可以检查。这就是为什么尝试调用委托的代码必须首先进行检查。

答案 5 :(得分:1)

我猜它的原因是历史和一致性。

与其他类型的处理方式一致;例如在处理null时没有语言或平台的帮助 - 程序员有责任确保从不实际使用空占位符 - 如果你希望调用它的对象引用,则无法选择性地调用方法毕竟,非空。

这是历史,因为在大多数类型中默认包含空引用,即使这不是必需的。也就是说,不是像值类型那样处理引用类型,而是需要额​​外的“可空性”注释来允许可空性,引用类型总是可以为空。我确信在设计Java和.NET的那一天有这个原因,但是它引入了许多不必要的错误和复杂性,这些错误和复杂性在强类型语言中很容易避免(例如,.NET值类型)。但是,鉴于历史上将null列为类型系统范围内的“无效”值,可以说,对代表来说这样做也是很自然的。

答案 6 :(得分:1)

  

“因为代表或多或少都是公正的   我会假设一系列功能,   这可能很容易   由代表本身检查“。

你的假设是错误的。简单明了。