为什么委托不处理事件 null
调用委托时,您始终必须检查它是否不为空。这通常是导致错误的原因。 由于委托或多或少只是一个函数列表,我认为委托本身可以轻松检查这一点。
有谁知道为什么要这样实施?
When calling a delegate you always have to check if it is not null. This is an often cause for errors.
Since delegates are more or less just a list of functions, I would assume, that this could have been easily checked by the delegate itself.
Does anybody know, why it has been implemented as it is?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
这可能是显而易见的,但您可以声明事件指向无操作处理程序,然后在调用时不需要检查 null。
然后,您可以调用
MyEvent
指向的处理程序,而无需检查 null。This may be stating the obvious, but you can declare your event to point to a no-op handler and then you don't need to check for null when invoking.
Then you can call the handlers pointed to by
MyEvent
without checking for null.委托本身无法检查它。由于它为 null,因此您无法调用它的方法。
另一方面,C# 编译器可以自动插入空检查。但如果委托是具有返回值的函数,我不知道要使用什么行为。
有人可能会认为 C# 编译器应该插入对 void 函数的检查,以避免在这种常见情况下出现样板代码。但这个决定现在已经成为过去,如果你有一台时间机器,你只能在不破坏现有程序的情况下修复它。
您可以使用扩展方法(因为可以在空对象上调用它们)来自动对事件进行空检查:
http://blogs.microsoft.co.il/blogs/shayf/archive/2009/05/25/a-handy-extension-method-raise-events-safely.aspx
由于参数是临时变量,因此您无需为了线程安全而手动将事件分配给临时变量。
The delegate itself can't check it. Since it is null you can't call methods on it.
The C# compiler on the other hand could automatically insert the null-check. But I don't know what behavior to use if the delegate is a function with a return-value.
One might argue that the C# compiler should insert that check for void-functions to avoid boilerplate code in that common case. But that decision is now in the past, you can only fix it without breaking existing programs if you get a time-machine.
You can use extension methods(since they can be called on a null object) to automate the null-check for events:
http://blogs.microsoft.co.il/blogs/shayf/archive/2009/05/25/a-handy-extension-method-raise-events-safely.aspx
And since a parameter is a temp variable you don't need to manually assign the event to a temp variable for thread-safety.
在内部,编译器将为类中声明的每个事件生成 2 个方法 add_MyEvent 和 remove_MyEvent。当您编写 MyEvent += ... 时,编译器实际上会生成对 add_MyEvent 的调用,而 add_MyEvent 又会调用 System.Delegate.Combine。
组合在参数中接受 2 个委托,并从 2 个原始委托创建一个新的(多播)委托,处理其中一个为 null 的情况(这是您第一次调用 += 时的情况)。
我想编译器可以更聪明一点,并且还可以处理事件调用,以便当您调用
MyEvent(),它实际上会生成一个空检查,并且仅当不为空时才实际调用委托。 (如果能得到埃里克·利珀特(Eric Lippert)对此的看法就好了)。
Internally, the compiler will generate 2 methods add_MyEvent and remove_MyEvent for each event declared in a class. When you write, MyEvent += ..., the compiler will actually generate a call to add_MyEvent which in turn will call System.Delegate.Combine.
Combine takes 2 delegates in parameter and creates a new (multicast) delegate from the 2 original delegates, handling the case when one of them is null (which is the case the first time you call +=).
I guess the compiler could have been a bit smarter and also handled the event call so that when you call
MyEvent(), it would actually generate a null check and actually invoke the delegate only if not null. (It would be nice to have Eric Lippert's view on that one).
原因是性能(实际上,这是我最好的猜测)。事件和委托是通过大量编译器魔法创建的,这些东西无法仅通过 C# 代码来复制。但其本质是这样的:
委托是一个编译器生成的类,它继承自
MulticastDelegate
类,而该类本身又派生自Delegate
类。请注意,这两个类很神奇,您无法自己继承它们(好吧,也许您可以,但您将无法很好地使用它们)。然而,一个事件的实现是这样的:
好吧,所以这不是真正的代码(也不完全是现实生活中的方式),但它应该让您了解正在发生的事情。
关键是支持字段最初确实为空。这样,在创建对象时就不会产生创建
MyEventDelegateClass
实例的开销。这就是为什么在调用之前必须检查 null 的原因。稍后,当添加/删除处理程序时,将创建一个 MyEventDelegateClass 实例并将其分配给该字段。当最后一个处理程序被删除时,该实例也会丢失,并且支持字段再次重置为 null。这就是“不使用不付费”的原则。只要您不使用该事件,就不会产生任何开销。没有额外的内存,没有额外的 CPU 周期。
The reason is performance (actually, that's my best guess). Events and delegates are made with a lot of compiler magic, stuff that cannot be replicated by mere C# code. But underneath it goes something like this:
A delegate is a compiler-generated class that inherits from
MulticastDelegate
class, which itself derives from theDelegate
class. Note these two classes are magic and you can't inherit from them yourself (well, maybe you can, but you won't be able to use them very well).An event however is implemented something like this:
OK, so this isn't real code (nor is it exactly the way it is in real life), but it should give you an idea of what's going on.
The point is that the backing field initially really is null. That way there is no overhead for creating an instance of
MyEventDelegateClass
when creating your object. And that's why you have to check for null before invoking. Later on, when handlers get added/removed, an instance ofMyEventDelegateClass
is created and assigned to the field. And when the last handler is removed, this instance is also lost and the backing field is reset to null again.This is the principle of "you don't pay for what you don't use". As long as you don't use the event, there will be no overhead for it. No extra memory, no extra CPU cycles.
我猜原因是历史和一致性。
与其他类型的处理方式一致;例如,在处理 null 时没有语言或平台帮助 - 程序员有责任确保 null 占位符永远不会被实际使用 - 如果您希望引用对象引用,则无法仅选择性地调用方法毕竟,call it on 也不为空。
这是历史,因为大多数类型默认情况下都包含空引用,尽管这不是必需的。也就是说,引用类型始终可以为空,而不是将引用类型视为值类型并需要额外的“可空性”注释来允许可为空。我确信在设计 Java 和 .NET 时,这是有原因的,但它引入了许多不必要的错误和复杂性,而这些错误和复杂性在强类型语言(例如 .NET 值类型)中很容易避免。但是考虑到历史上将
null
包含为类型系统范围内的“无效”值,可以这么说,对于委托来说这样做也是很自然的。I'd guess the reasons are history and consistency.
It is consistent with the way other types are handled; e.g. there's no language or platform assistence in dealing with
null
s - it's the programmers responsibility to ensure the null placeholder is never actually used - there's no means of only optionally calling a method if the object reference you wish to call it on is non-null either, after all.It's history, since null references happen to be included by default in most types, even though this isn't necessary. That is, rather than treating reference types like value types and require an additional "nullability" annotation to permit nullability, reference types are always nullable. I'm sure there were reasons for this back in the day when Java and .NET were designed, but it introduces many unnecessary bugs and complexities which are easy to avoid in a strongly typed language (e.g., .NET value types). But given the historical inclusion of
null
as a type-system wide "invalid" value, so to speak, it's only natural to do so for delegates as well.如果委托实例为
null
,它如何“检查自身”?它是null
...这意味着它基本上不存在。首先没有什么可以自我检查的。这就是为什么尝试调用委托的代码必须首先进行该检查的原因。If a delegate instance is
null
, how can it "check itself"? It'snull
... that means it does not exist, basically. There is nothing there to check itself in the first place. That's why your code which attempts to call the delegate must do that checking, first.你的假设是错误的。简单明了。
Your assumption is wrong. Plain and simple.