C# 在事件声明中添加匿名空委托有什么缺点吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/170907/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is there a downside to adding an anonymous empty delegate on event declaration?
提问by serg10
I have seen a few mentions of this idiom (including on SO):
我已经看到一些提到这个习语(包括在 SO 上):
// Deliberately empty subscriber
public event EventHandler AskQuestion = delegate {};
The upside is clear - it avoids the need to check for null before raising the event.
好处很明显 - 它避免了在引发事件之前检查 null 的需要。
However, I am keen to understand if there are any downsides.For example, is it something that is in widespread use and is transparent enough that it won't cause a maintenance headache? Is there any appreciable performance hit of the empty event subscriber call?
但是,我很想知道是否有任何缺点。例如,它是否被广泛使用并且足够透明,不会引起维护问题?空事件订阅者调用是否有任何明显的性能影响?
采纳答案by Maurice
The only downside is a very slight performance penalty as you are calling extra empty delegate. Other than that there is no maintenance penalty or other drawback.
唯一的缺点是非常轻微的性能损失,因为您正在调用额外的空委托。除此之外,没有维护罚款或其他缺点。
回答by Marc Gravell
If you are doing it a /lot/, you might want to have a single, static/shared empty delegate that you re-use, simply to reduce the volume of delegate instances. Note that the compiler caches this delegate per event anyway (in a static field), so it is only one delegate instance per event definition, so it isn't a hugesaving - but maybe worthwhile.
如果您在 /lot/ 中执行此操作,您可能希望有一个单独的、静态/共享的空委托供您重用,只是为了减少委托实例的数量。请注意,编译器无论如何都会缓存每个事件的这个委托(在静态字段中),所以每个事件定义只有一个委托实例,所以这不是一个巨大的节省 - 但可能值得。
The per-instance field in each class will still take the same space, of course.
当然,每个类中的每个实例字段仍将占用相同的空间。
i.e.
IE
internal static class Foo
{
internal static readonly EventHandler EmptyEvent = delegate { };
}
public class Bar
{
public event EventHandler SomeEvent = Foo.EmptyEvent;
}
Other than that, it seems fine.
除此之外,似乎还好。
回答by Kent Boogaart
For systems that make heavy use of events and are performance-critical, you will definitely want to at least considernot doing this. The cost for raising an event with an empty delegate is roughly twice that for raising it with a null check first.
对于大量使用事件并且对性能至关重要的系统,您肯定至少要考虑不这样做。使用空委托引发事件的成本大约是首先使用空检查引发事件的成本的两倍。
Here are some figures running benchmarks on my machine:
以下是在我的机器上运行基准测试的一些数字:
For 50000000 iterations . . .
No null check (empty delegate attached): 530ms
With null check (no delegates attached): 249ms
With null check (with delegate attached): 452ms
And here is the code I used to get these figures:
这是我用来获取这些数字的代码:
using System;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
public event EventHandler<EventArgs> EventWithDelegate = delegate { };
public event EventHandler<EventArgs> EventWithoutDelegate;
static void Main(string[] args)
{
//warm up
new Program().DoTimings(false);
//do it for real
new Program().DoTimings(true);
Console.WriteLine("Done");
Console.ReadKey();
}
private void DoTimings(bool output)
{
const int iterations = 50000000;
if (output)
{
Console.WriteLine("For {0} iterations . . .", iterations);
}
//with anonymous delegate attached to avoid null checks
var stopWatch = Stopwatch.StartNew();
for (var i = 0; i < iterations; ++i)
{
RaiseWithAnonDelegate();
}
stopWatch.Stop();
if (output)
{
Console.WriteLine("No null check (empty delegate attached): {0}ms", stopWatch.ElapsedMilliseconds);
}
//without any delegates attached (null check required)
stopWatch = Stopwatch.StartNew();
for (var i = 0; i < iterations; ++i)
{
RaiseWithoutAnonDelegate();
}
stopWatch.Stop();
if (output)
{
Console.WriteLine("With null check (no delegates attached): {0}ms", stopWatch.ElapsedMilliseconds);
}
//attach delegate
EventWithoutDelegate += delegate { };
//with delegate attached (null check still performed)
stopWatch = Stopwatch.StartNew();
for (var i = 0; i < iterations; ++i)
{
RaiseWithoutAnonDelegate();
}
stopWatch.Stop();
if (output)
{
Console.WriteLine("With null check (with delegate attached): {0}ms", stopWatch.ElapsedMilliseconds);
}
}
private void RaiseWithAnonDelegate()
{
EventWithDelegate(this, EventArgs.Empty);
}
private void RaiseWithoutAnonDelegate()
{
var handler = EventWithoutDelegate;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
}
回答by Christopher Bennage
It is my understanding that the empty delegate is thread safe, whereas the null check is not.
我的理解是空委托是线程安全的,而空检查不是。
回答by Scott P
I would say it's a bit of a dangerous construct, because it tempts you to do something like :
我会说这是一个有点危险的结构,因为它诱使你做类似的事情:
MyEvent(this, EventArgs.Empty);
If the client throws an exception, the server goes with it.
如果客户端抛出异常,服务器会随之处理。
So then, maybe you do:
那么,也许你会这样做:
try
{
MyEvent(this, EventArgs.Empty);
}
catch
{
}
But, if you have multiple subscribers and one subscriber throws an exception, what happens to the other subscribers?
但是,如果您有多个订阅者并且一个订阅者抛出异常,那么其他订阅者会发生什么?
To that end, I've been using some static helper methods that do the null check and swallows any exception from the subscriber side (this is from idesign).
为此,我一直在使用一些静态辅助方法来进行空检查并吞下来自订阅者端的任何异常(这是来自 idesign)。
// Usage
EventHelper.Fire(MyEvent, this, EventArgs.Empty);
public static void Fire(EventHandler del, object sender, EventArgs e)
{
UnsafeFire(del, sender, e);
}
private static void UnsafeFire(Delegate del, params object[] args)
{
if (del == null)
{
return;
}
Delegate[] delegates = del.GetInvocationList();
foreach (Delegate sink in delegates)
{
try
{
sink.DynamicInvoke(args);
}
catch
{ }
}
}
回答by Judah Gabriel Himango
Instead of inducing performance overhead, why not use an extension methodto alleviate both problems:
与其引入性能开销,不如使用扩展方法来缓解这两个问题:
public static void Raise(this EventHandler handler, object sender, EventArgs e)
{
if(handler != null)
{
handler(sender, e);
}
}
Once defined, you never have to do another null event check again:
一旦定义,您就不必再次进行空事件检查:
// Works, even for null events.
MyButtonClick.Raise(this, EventArgs.Empty);
回答by vkelman
回答by Thomas
One thing is missed out as an answer for this question so far: It is dangerous to avoid the check for the null value.
到目前为止,这个问题的答案遗漏了一件事:避免检查 null value 是危险的。
public class X
{
public delegate void MyDelegate();
public MyDelegate MyFunnyCallback = delegate() { }
public void DoSomething()
{
MyFunnyCallback();
}
}
X x = new X();
x.MyFunnyCallback = delegate() { Console.WriteLine("Howdie"); }
x.DoSomething(); // works fine
// .. re-init x
x.MyFunnyCallback = null;
// .. continue
x.DoSomething(); // crashes with an exception
The thing is: You never know who will use your code in which way. You never know, if in some years during a bug fix of your code the event/handler is set to null.
问题是:你永远不知道谁会以何种方式使用你的代码。您永远不知道,如果在几年内修复代码的错误期间,事件/处理程序被设置为 null。
Always, write the if check.
总是,写 if 检查。
Hope that helps ;)
希望有帮助;)
ps: Thanks for the performance calculation.
ps:感谢性能计算。
pps: Edited it from a event case to and callback example. Thanks for the feedback ... I "coded" the example w/o Visual Studio and adjusted the example I had in mind to an event. Sorry for the confusion.
pps:将其从事件案例编辑为和回调示例。感谢您的反馈......我在没有 Visual Studio 的情况下“编码”了示例,并将我想到的示例调整为一个事件。对困惑感到抱歉。
ppps: Do not know if it still fits to the thread ... but I think it is an important principle. Please also check another thread of stackflow
回答by dasblinkenlight
There is no meaningful performance penalty to talk about, except, possibly, for some extreme situations.
除了可能的某些极端情况外,没有任何有意义的性能损失可谈。
Note, however, that this trick becomes less relevant in C# 6.0, because the language provides an alternative syntax to calling delegates that may be null:
但是请注意,此技巧在 C# 6.0 中变得不那么重要,因为该语言提供了一种替代语法来调用可能为 null 的委托:
delegateThatCouldBeNull?.Invoke(this, value);
Above, null conditional operator ?.
combines null checking with a conditional invocation.
上面,空条件运算符?.
将空检查与条件调用结合起来。