在事件声明中添加匿名空委托有缺点吗?

发布于 2024-07-06 21:21:08 字数 362 浏览 6 评论 0原文

我见过一些关于这个习语的提及(包括on SO):

// Deliberately empty subscriber
public event EventHandler AskQuestion = delegate {};

好处是clear - 它避免了在引发事件之前检查 null 的需要。

但是,我很想知道是否有任何缺点。例如,它是否被广泛使用并且足够透明,不会导致维护麻烦? 空事件订阅者调用是否会对性能造成明显影响?

I have seen a few mentions of this idiom (including on 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.

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

巡山小妖精 2024-07-13 21:21:08

为什么不使用扩展方法来缓解这两个问题,而不是引起性能开销:

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

一次定义后,您不必再次进行空事件检查:

// Works, even for null events.
MyButtonClick.Raise(this, EventArgs.Empty);

Instead of inducing performance overhead, why not use an extension method to 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);
帅哥哥的热头脑 2024-07-13 21:21:08

对于大量使用事件并且对性能至关重要的系统,您肯定至少考虑不这样做。 使用空委托引发事件的成本大约是首先使用空检查引发事件的成本的两倍。

以下是在我的机器上运行基准测试的一些数据:

For 50000000 iterations . . .
No null check (empty delegate attached): 530ms
With null check (no delegates attached): 249ms
With null check (with delegate attached): 452ms

这是我用来获取这些数据的代码:

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);
            }
        }
    }
}

For systems that make heavy use of events and are performance-critical, you will definitely want to at least consider not 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);
            }
        }
    }
}
涫野音 2024-07-13 21:21:08

唯一的缺点是,当您调用额外的空委托时,性能会受到非常轻微的影响。 除此之外,没有维护惩罚或其他缺点。

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.

人间不值得 2024-07-13 21:21:08

如果您经常这样做,您可能希望有一个可重复使用的单个静态/共享空委托,只是为了减少委托实例的数量。 请注意,编译器无论如何都会缓存每个事件的此委托(在静态字段中),因此每个事件定义只有一个委托实例,因此这不是一个巨大的节省 - 但也许是值得的。

当然,每个类中的每个实例字段仍将占用相同的空间。

internal static class Foo
{
    internal static readonly EventHandler EmptyEvent = delegate { };
}
public class Bar
{
    public event EventHandler SomeEvent = Foo.EmptyEvent;
}

除此之外,似乎还不错。

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 huge saving - but maybe worthwhile.

The per-instance field in each class will still take the same space, of course.

i.e.

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.

浪推晚风 2024-07-13 21:21:08

除了可能的某些极端情况之外,没有什么有意义的性能损失可讨论。

但请注意,此技巧在 C# 6.0 中变得不太相关,因为该语言提供了一种替代语法来调用可能为 null 的委托:

delegateThatCouldBeNull?.Invoke(this, value);

上面,null 条件运算符 ?. 将 null 检查与条件调用结合在一起。

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:

delegateThatCouldBeNull?.Invoke(this, value);

Above, null conditional operator ?. combines null checking with a conditional invocation.

清风夜微凉 2024-07-13 21:21:08

据我了解,空委托是线程安全的,而空检查则不是。

It is my understanding that the empty delegate is thread safe, whereas the null check is not.

谢绝鈎搭 2024-07-13 21:21:08

我想说这是一个有点危险的构造,因为它诱惑你做类似的事情:

MyEvent(this, EventArgs.Empty);

如果客户端抛出异常,服务器也会随之而来。

那么,也许您会这样做:

try
{
  MyEvent(this, EventArgs.Empty);
}
catch
{
}

但是,如果您有多个订阅者并且其中一个订阅者抛出异常,那么其他订阅者会发生什么情况?

为此,我一直在使用一些静态辅助方法来执行空检查并吞掉订阅者端的任何异常(这来自 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
        { }
    }
}

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).

// 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
        { }
    }
}
回眸一笑 2024-07-13 21:21:08

除了“空委托”方法之外,我们还可以定义一种简单的扩展方法来封装检查事件处理程序是否为 null 的传统方法。 此处此处

Instead of "empty delegate" approach one can define a simple extension method to encapsulate the conventional method of checking event handler against null. It is described here and here.

浴红衣 2024-07-13 21:21:08

到目前为止,这个问题的答案被遗漏了:避免检查 null 值是危险的

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

问题是:您永远不知道谁会以哪种方式使用您的代码。 您永远不知道,在某些年里,在代码错误修复期间,事件/处理程序是否设置为空。

始终写下 if 检查。

希望有帮助;)

ps:感谢您的性能计算。

pps:将其从事件案例编辑为回调示例。 感谢您的反馈...我在没有 Visual Studio 的情况下“编码”了该示例,并将我想到的示例调整为一个事件。 对困惑感到抱歉。

ppps:不知道它是否仍然适合线程......但我认为这是一个重要的原则。 另请检查 stackflow 的另一个线程

One thing is missed out as an answer for this question so far: It is dangerous to avoid the check for the 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.

Always, write the if check.

Hope that helps ;)

ps: Thanks for the performance calculation.

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.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文