79

Possible Duplicate:
C#: Difference between ‘ += anEvent’ and ‘ += new EventHandler(anEvent)’

There are two basic ways to subscribe to an event:

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

What is the difference, and when should I chose one over the other?

Edit: If it is the same, then why does VS default to the long version, cluttering the code? That makes no sense at all to me.

Community
  • 1
  • 1
mafu
  • 31,798
  • 42
  • 154
  • 247
  • 6
    Code generators clutter more often (using long qualified names instead of using directives). They aim for ease of generation and avoiding errors, not for readability. – H H May 01 '10 at 14:45
  • 1
    I was specifically talking about the code snippet, which is not used by an automated code generator. – mafu May 01 '10 at 16:34
  • 1
    So was I. The long forms are less likely to create ambiguities. – H H May 01 '10 at 17:52
  • @Henk I see, yes. I guess that makes sense. :) – mafu May 01 '10 at 19:21

5 Answers5

55

Since there seemed to be some dispute over my original answer, I decided to do a few tests, including looking at the generated code and monitoring the performance.

First of all, here's our test bed, a class with a delegate and another class to consume it:

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

First thing to do is look at the generated 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 
}

So it turns out that, yes, these do generate identical IL. I was wrong originally. But that's not the whole story. It may be that I'm going off-topic here but I think that it's important to include this when talking about events and delegates:

Creating and comparing different delegates is not cheap.

When I wrote this, I was thinking that the first syntax was able to cast the method group as a delegate, but it turns out that it's just a conversion. But it's completely different when you actually save the delegate. If we add this to the consumer:

class Counter
{
    EventHandler savedEvent;

    public Counter()
    {
        savedEvent = CountEvent;
    }

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

You can see that this has very different characteristics, performance-wise, from the other two:

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

The results consistently come back as something similar to:

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

That's nearly a 20% difference when using a saved delegate vs. creating a new one.

Now obviously not every program is going to be adding and removing this many delegates in such a small amount of time, but if you're writing library classes - classes that might be used in ways you cannot predict - then you really want to keep this difference in mind if you ever need to add and remove events (and I've written a lot of code that does this, personally).

So the conclusion of this is, writing SomeEvent += new EventHandler(NamedMethod) compiles to the same thing as just SomeEvent += NamedMethod. But if you plan to remove that event handler later, you really should save the delegate. Even though the Delegate class has some special-case code that allows you to remove a referentially-different delegate from the one you added, it has to do a non-trivial amount of work to pull this off.

If you're not going to save the delegate, then it makes no difference - the compiler ends up creating a new delegate anyway.

Aaronaught
  • 120,909
  • 25
  • 266
  • 342
  • This is totally wrong (and only partially corrected). There is no difference between the two forms, neither for `+=` or `-=` . They are just different notations for the same thing. – H H May 01 '10 at 14:36
  • @Aero, I read the linked stuff, it ends with a demonstration that the 2 forms _are_ the same and does not proof that one form is quicker or better. – H H May 01 '10 at 14:50
  • 2
    And just to be sure I wrote a little test and looked at the IL. The 2 forms produce __identical__ IL code. – H H May 01 '10 at 15:02
  • @Henk: Looks like I was getting two different issues confused. I've removed the incorrect information and my earlier comment (apologies if it came off as confrontational). – Aaronaught May 01 '10 at 15:48
  • +1 Interesting, thanks for the background info. – mafu May 01 '10 at 16:41
  • 1
    @Aaro, yes, this is an optimization but not one that I would recommend for regular practice. And while it is related to the original question, it is not answering it. – H H May 01 '10 at 17:55
  • @Henk: Now you're just being difficult. The first half of this post very clearly answers the question *and* includes information to back up that answer; and last time I checked, there is no rule or guideline on this site that answers shouldn't *also* include pertinent information that may not have been specifically asked for. As for the first part of your comment, you don't define *regular practice* so that doesn't seem to mean much. – Aaronaught May 01 '10 at 18:36
  • 1
    @Aaron: I personally don't feel this answer is any more correct than mine (which you actually disagrees with originally). All it does, in a much longer post, is explain what I said...they are exactly the same. I also agree with @henk I don't think it is good practise to "save" event handlers, you are doing micro optimizations when in fact it may not even be relevant (as the OP doesn't even mention anything about unassigning the events) – James May 01 '10 at 21:43
  • 1
    @James: I think the benchmark clearly shows that it is not a "micro" optimization. If you don't want to do it, fine, but if you're going to say it's a bad practice, best cite some evidence for that. I used to save delegates in constructors this way all the time before I (mistakenly) started thinking that it was automatic using the second syntax; it certainly never caused any problems. The OP may not have mentioned anything about unsubscribing from event handlers, but it's pretty rare to subscribe this way at runtime and never unsubscribe. – Aaronaught May 01 '10 at 21:52
  • 1
    @Aaron: IMO it's a small performance optimization, you would only see the benefit if you were subscribing/unsubscribing events all over the place. So unless my app was mission critical I would definately not be "saving" event handlers...I do take your point though. – James May 02 '10 at 00:45
  • 2
    A 20% saving on an operation that takes maybe 100 ms? Come on. – Timwi Apr 22 '11 at 14:47
  • 1
    @Timwi: Read the whole post. *"...obviously not every program is going to be adding and removing this many delegates in such a small amount of time, but **if you're writing library classes**..."* I agree that we shouldn't micro-optimize for no reason, but the determining factor is how your code is going to be used, not how long it takes to execute one iteration. Application code, don't bother; library code, maybe. – Aaronaught Apr 22 '11 at 15:45
  • +1 @Aaronaught personally, I love these kinds of answers. Even when the answer is *"you don't typically need to worry about it"*, I always like to know *"why"* :) – HodlDwon Apr 13 '13 at 16:37
  • +1 @Aaronaught I've learned so much from this post. It IS important to question why one method is slower than another and what is happening under the hood. It helps to understand things. Such things like "you are doing micro optimization" or "20% saving on an operation that takes maybe 100 ms" are counterproductive. – j00hi May 29 '13 at 23:56
  • cool! nice finding – Maverick Meerkat Apr 23 '18 at 12:49
  • @Timwi, around 2 seconds for 10'000'000 operations gives 200ns (nano second) per operation. Indeed this saving looks rather academic. – Wernfried Domscheit Nov 07 '18 at 15:08
26

There is no difference from a programming perspective, they are each others equivalent. The compiler will pretty much do what you have done on the first line with the second line behind the scenes. So I would always opt for the second approach (less code).

Re: Your Edit

Probably because they feel its better to show developers the proper way of doing things rather than shortcuts. Your guess is as good as mine :)

James
  • 80,725
  • 18
  • 167
  • 237
9

the second form is syntactic sugar introduced in later versions of c#. the first line will work in every version though

knittl
  • 246,190
  • 53
  • 318
  • 364
7

There's no difference. Prior to .NET 2.0, every variable assignments must be of exact type, compilers then didn't infer much. So as to make a work-around, VS 2003 emit new EventHandler around the function name. That's just my guess. Because..

I tried something now in VS 2008, textBox1.KeyDown += (KeyEventHandler)textBox1_KeyDown, that also work. It puzzles me why they choose new EventHandler(checkBox1_CheckStateChanged), rather than (EventHandler)checkBox1_CheckStateChanged then. But...

as I don't have VS 2003 in my box anymore, I can't pin down if the casting approach could also work on VS 2003. But afaict, I tried removing new EventHandler on function name when I used VS 2003 (.NET 1.1), deeming why the need to instantiate (new EventHandler) a function, delegates are just function pointer under the hood, but it doesn't work.

It's only from .NET 2.0 onwards that C# compiler started to infer as much as possible.

This article http://blueonionsoftware.com/blog.aspx?p=aed2ae46-7548-4e5f-83c6-95e00c6f3649 supported my memory of new EventHandler prior to .NET 2.0 compilers, it was a compulsary

[EDIT]

The following article goes in depth about subscribing/unsubcribing events, purporting there's a difference between button1.Click += new EventHandler(button1_Click); and button1.Click += button1_Click;, but sadly I can't see any difference in IL level though :-(

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

Michael Buen
  • 38,643
  • 9
  • 94
  • 118
  • No, the long version isn't a workaround, it is the full syntax. The other one is just shorthand that the compiler rewrites to the long version. – H H May 01 '10 at 12:23
  • @Henk Holterman: I deleted my answer, then I edited my answer heavily. I'm not vaguely sure that it was not compulsary then, so before I try to write what I can remember and undelete, I tried to google first of an article that supported of things I remembered then. The long form was compulsary, but I can't posit if it's workaround on explicit type assigment compatibilities of compilers then, I'm just starting to learn ildasm now :-) I don't know what the instance instruction do in the IL i'm checking now – Michael Buen May 01 '10 at 13:23
  • The long form was necessary in .NET 1.1 and before. C#2 introduced the short form. Along with anonymous methods, a step towards lambdas. – H H May 01 '10 at 14:38
2

There is no difference, the first one just is more specific in its definition.

Thomas
  • 7,933
  • 4
  • 37
  • 45