tags:

views:

301

answers:

5

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.

+1  A: 

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

Thomas
+18  A: 

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
+1  A: 

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
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.
Henk Holterman
@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
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.
Henk Holterman
+2  A: 

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

knittl
+5  A: 

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
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.
Henk Holterman
@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.
Henk Holterman
And just to be sure I wrote a little test and looked at the IL. The 2 forms produce __identical__ IL code.
Henk Holterman
@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
+1 Interesting, thanks for the background info.
mafutrct
@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.
Henk Holterman
@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
@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
@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
@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