【问题标题】:+= new EventHandler(Method) vs += Method [duplicate]+= new EventHandler(Method) vs += Method [重复]
【发布时间】:2011-02-14 13:05:35
【问题描述】:

可能重复:
C#: Difference between ‘ += anEvent’ and ‘ += new EventHandler(anEvent)’

订阅事件有两种基本方式:

SomeEvent += new EventHandler<ArgType> (MyHandlerMethod);
SomeEvent += MyHandlerMethod;

有什么区别,我什么时候应该选择一个而不是另一个?

编辑:如果都一样,那为什么VS默认是长版,代码乱七八糟?这对我来说毫无意义。

【问题讨论】:

  • 代码生成器更加混乱(使用长限定名而不是使用指令)。它们的目标是易于生成和避免错误,而不是可读性。
  • 我专门说的是sn-p代码,自动代码生成器不使用它。
  • 我也是。长表格不太可能造成歧义。
  • @Henk 我明白了,是的。我想这是有道理的。 :)

标签: c# events


【解决方案1】:

没有区别,只是第一个定义更具体。

【讨论】:

    【解决方案2】:

    从编程的角度来看没有区别,它们是等价的。编译器几乎会做你在第一行所做的事情,而第二行在幕后。所以我总是选择第二种方法(更少的代码)。

    回复:您的编辑

    可能是因为他们觉得向开发人员展示正确的做事方式而不是捷径更好。你的猜测和我的一样好:)

    【讨论】:

      【解决方案3】:

      没有区别。在 .NET 2.0 之前,每个变量赋值都必须是精确的类型,编译器并没有进行太多推断。为了解决这个问题,VS 2003 在函数名周围发出new EventHandler。这只是我的猜测。因为..

      我现在在 VS 2008 中尝试了一些东西,textBox1.KeyDown += (KeyEventHandler)textBox1_KeyDown,它也可以工作。我很困惑他们为什么选择new EventHandler(checkBox1_CheckStateChanged),而不是(EventHandler)checkBox1_CheckStateChanged。但是……

      由于我的盒子里不再有 VS 2003,我无法确定铸造方法是否也适用于 VS 2003。但是,当我使用 VS 2003 时,我尝试在函数名称上删除 new EventHandler (.NET 1.1),认为为什么需要实例化(new EventHandler)一个函数,委托只是底层的函数指针,但它不起作用。

      只有从 .NET 2.0 开始,C# 编译器才开始尽可能多地进行推断。

      这篇文章http://blueonionsoftware.com/blog.aspx?p=aed2ae46-7548-4e5f-83c6-95e00c6f3649支持我对.NET 2.0编译器之前的new EventHandler的记忆,这是必修课

      [编辑]

      以下文章深入探讨了订阅/取消订阅事件,声称button1.Click += new EventHandler(button1_Click);button1.Click += button1_Click; 之间存在差异,但遗憾的是我看不出 IL 级别有任何差异 :-(

      http://blogs.msdn.com/abhinaba/archive/2005/08/26/456437.aspx

      【讨论】:

      • 不,长版本不是解决方法,它是完整的语法。另一种只是编译器重写为长版本的简写。
      • @Henk Holterman:我删除了我的答案,然后我大量编辑了我的答案。我不确定那不是强制性的,所以在我尝试写我能记住和取消删除的内容之前,我首先尝试在谷歌上搜索一篇支持我当时记得的事情的文章。长形式是强制性的,但我不能确定它是否是编译器的显式类型分配兼容性的解决方法,我现在才开始学习 ildasm :-) 我不知道实例指令在 IL i 中做了什么正在检查中
      • 长格式在 .NET 1.1 及之前的版本中是必需的。 C#2 引入了缩写形式。与匿名方法一起,迈向 lambdas。
      【解决方案4】:

      第二种形式是后来的 c# 版本中引入的语法糖。第一行将适用于每个版本

      【讨论】:

        【解决方案5】:

        由于我的原始答案似乎存在争议,我决定做一些测试,包括查看生成的代码监控性能。

        首先,这是我们的测试平台,一个带有委托的类和另一个使用它的类:

        class EventProducer
        {
            public void Raise()
            {
                var handler = EventRaised;
                if (handler != null)
                    handler(this, EventArgs.Empty);
            }
        
            public event EventHandler EventRaised;
        }
        
        class Counter
        {
            long count = 0;
            EventProducer producer = new EventProducer();
        
            public void Count()
            {
                producer.EventRaised += CountEvent;
                producer.Raise();
                producer.EventRaised -= CountEvent;
            }
        
            public void CountWithNew()
            {
                producer.EventRaised += new EventHandler(CountEvent);
                producer.Raise();
                producer.EventRaised -= new EventHandler(CountEvent);
            }
        
            private void CountEvent(object sender, EventArgs e)
            {
                count++;
            }
        }
        

        首先要做的是查看生成的 IL:

        .method public hidebysig instance void Count() cil managed
        {
            .maxstack 8
            L_0000: ldarg.0 
            L_0001: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
            L_0006: ldarg.0 
            L_0007: ldftn instance void DelegateTest.Program/Counter::CountEvent(object, class [mscorlib]System.EventArgs)
            L_000d: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
            L_0012: callvirt instance void DelegateTest.Program/EventProducer::add_EventRaised(class [mscorlib]System.EventHandler)
            L_0017: ldarg.0 
            L_0018: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
            L_001d: callvirt instance void DelegateTest.Program/EventProducer::Raise()
            L_0022: ldarg.0 
            L_0023: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
            L_0028: ldarg.0 
            L_0029: ldftn instance void DelegateTest.Program/Counter::CountEvent(object, class [mscorlib]System.EventArgs)
            L_002f: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
            L_0034: callvirt instance void DelegateTest.Program/EventProducer::remove_EventRaised(class [mscorlib]System.EventHandler)
            L_0039: ret 
        }
        
        .method public hidebysig instance void CountWithNew() cil managed
        {
            .maxstack 8
            L_0000: ldarg.0 
            L_0001: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
            L_0006: ldarg.0 
            L_0007: ldftn instance void DelegateTest.Program/Counter::CountEvent(object, class [mscorlib]System.EventArgs)
            L_000d: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
            L_0012: callvirt instance void DelegateTest.Program/EventProducer::add_EventRaised(class [mscorlib]System.EventHandler)
            L_0017: ldarg.0 
            L_0018: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
            L_001d: callvirt instance void DelegateTest.Program/EventProducer::Raise()
            L_0022: ldarg.0 
            L_0023: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
            L_0028: ldarg.0 
            L_0029: ldftn instance void DelegateTest.Program/Counter::CountEvent(object, class [mscorlib]System.EventArgs)
            L_002f: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
            L_0034: callvirt instance void DelegateTest.Program/EventProducer::remove_EventRaised(class [mscorlib]System.EventHandler)
            L_0039: ret 
        }
        

        所以事实证明,是的,它们确实生成了相同的 IL。原来我错了。但这不是全部。可能是我在这里跑题了,但我认为在谈论事件和代表时包含这一点很重要:

        创建和比较不同的代表并不便宜。

        当我写这篇文章时,我认为第一个语法能够将方法组转换为委托,但事实证明这只是一个转换。但是当您真正保存委托时,情况就完全不同了。如果我们将其添加到消费者:

        class Counter
        {
            EventHandler savedEvent;
        
            public Counter()
            {
                savedEvent = CountEvent;
            }
        
            public void CountSaved()
            {
                producer.EventRaised += savedEvent;
                producer.Raise();
                producer.EventRaised -= savedEvent;
            }
        }
        

        您可以看到,这在性能方面与其他两个具有非常不同的特征:

        static void Main(string[] args)
        {
            const int TestIterations = 10000000;
        
            TimeSpan countTime = TestCounter(c => c.Count());
            Console.WriteLine("Count: {0}", countTime);
        
            TimeSpan countWithNewTime = TestCounter(c => c.CountWithNew());
            Console.WriteLine("CountWithNew: {0}", countWithNewTime);
        
            TimeSpan countSavedTime = TestCounter(c => c.CountSaved());
            Console.WriteLine("CountSaved: {0}", countSavedTime);
        
            Console.ReadLine();
        }
        
        static TimeSpan TestCounter(Action<Counter> action, int iterations)
        {
            var counter = new Counter();
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < TestIterations; i++)
                action(counter);
            sw.Stop();
            return sw.Elapsed;
        }
        

        结果始终以类似于以下内容的形式返回:

        Count: 00:00:02.4742007
        CountWithNew: 00:00:02.4272702
        CountSaved: 00:00:01.9810367
        

        在使用保存的委托与创建新委托时,这几乎有 20% 的差异。

        现在显然不是每个程序都会在这么短的时间内添加和删除这么多委托,但是如果您正在编写库类 - 可能以您无法预测的方式使用的类 - 那么您真的想要如果您需要添加 和删除 事件,请牢记这一差异(我个人已经编写了很多代码)。

        因此得出的结论是,编写 SomeEvent += new EventHandler(NamedMethod) 编译为与 SomeEvent += NamedMethod 相同的内容。但是,如果您打算稍后删除该事件处理程序,您确实应该保存委托。尽管 Delegate 类有一些特殊情况代码允许您从您添加的委托中删除一个引用不同的委托,但它必须做大量的工作才能实现这一点。

        如果您不打算保存委托,那么它没有任何区别 - 编译器最终会创建一个新委托。

        【讨论】:

        • 为了确保我写了一个小测试并查看了 IL。这两种形式产生相同的 IL 代码。
        • @Aaro,是的,这是一种优化,但不是我推荐的常规练习。虽然它与原始问题有关,但它没有回答它。
        • @Aaron:我个人认为这个答案并不比我的更正确(实际上你最初并不同意)。在更长的帖子中,它所做的只是解释我所说的……它们完全相同。我也同意@henk 我认为“保存”事件处理程序不是一个好习惯,你正在做微优化,而实际上它甚至可能不相关(因为 OP 甚至没有提到任何关于取消分配事件)
        • @James:我认为基准测试清楚地表明它不是“微”优化。如果您不想这样做,很好,但如果您要说这是一种不好的做法,最好引用一些证据。在我(错误地)开始认为使用第二种语法是自动的之前,我一直以这种方式将委托保存在构造函数中;它当然从来没有引起任何问题。 OP 可能没有提到任何关于从事件处理程序中取消订阅的内容,但是在运行时以这种方式订阅并且从不取消订阅的情况非常罕见。
        • 在可能需要 100 毫秒的操作上节省 20%?来吧。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-08-08
        • 1970-01-01
        • 1970-01-01
        • 2013-11-15
        相关资源
        最近更新 更多