当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《在事件声明中添加匿名空委托是否有缺点?》经验,为你挑选了4个好方法。

我已经看到了一些关于这个习语的提及(包括SO):

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

好处很明显 - 它避免了在提升事件之前检查null的必要性.

但是,我很想知道是否有任何缺点. 例如,它是否被广泛使用并且足够透明以至于不会引起维护问题?空事件用户呼叫是否有明显的性能影响?



1> Judah Gabrie..:

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

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


实际上,这属于语言或框架!
没有.处理程序被传递给方法,此时,该实例无法修改.
这不是只是将null检查的线程问题移到你的扩展方法中吗?
请参阅此处获取通用版本:http://stackoverflow.com/questions/192980/boiler-plate-code-replacement-is-there-anything-bad-about-this-code

2> Kent Boogaar..:

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

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

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 EventWithDelegate = delegate { };
        public event EventHandler 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);
            }
        }
    }
}


布拉德,我特别谈到了大量使用事件的性能关键系统.那怎么样?
你在开玩笑,对吧?调用增加了5纳秒,你警告不要这样做吗?我想不出比这更不合理的一般优化.
有趣.根据你的发现,检查null并调用一个委托比在没有检查的情况下调用它更快.听起来不对我.但无论如何,这是一个很小的差异,除了最极端的情况之外,我认为它并不明显.
你在谈论空代表时的绩效惩罚.我问你:当有人真正订阅这个活动时会发生什么?您应该担心订阅者的性能而不是空委托的性能.
在没有"真实"订户的情况下,不会出现大的性能成本,但是在有一个订户的情况下.在这种情况下,订阅,取消订阅和调用应该非常有效,因为它们不必对调用列表执行任何操作,但事件(从系统的角度来看)有两个订阅者而不是一个订阅者将强制使用处理"n"个订阅者的代码,而不是针对"零"或"一个"情况优化的代码.
输出基准测试中存在误导性的侥幸,因为两个不同的对象用于预热和实际运行.因此,"预热"在第二次调用时无效,因为它只是预热了第二个对象.侥幸是由于第一次调用必须创建的空委托,这比简单的调用要长.如果你可以通过对两个调用使用相同的对象来纠正结果,那就太好了.:)

3> Maurice..:

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


如果事件本来只有一个订阅者(一个*非常常见的情况),虚拟处理程序将使它有两个.具有一个处理程序的事件比具有两个处理程序的事件处理*更多*.

4> Marc Gravell..:

如果您正在使用/ lot /,您可能希望拥有一个可重复使用的静态/共享空委托,只需减少委托实例的数量.请注意,编译器无论如何都会在每个事件中缓存此委托(在静态字段中),因此每个事件定义只有一个委托实例,因此它不是一个巨大的节省 - 但可能值得.

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

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

除此之外,它看起来很好.

推荐阅读
Gbom2402851125
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有