Since there seemed to be some debate over my initial answer, I decided to do some tests, including looking at the generated code and monitoring performance.
First of all, here is our test bench, a class with a delegate and another class to use 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++; } }
The 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, they generate an identical IL. At first I was wrong. But this is not the whole story . Maybe I'm leaving the topic, but I think itβs important to include this when it comes to events and delegates:
Creating and comparing different delegates is not cheap.
When I wrote this, I thought that the first syntax was able to distinguish a group of methods as a delegate, but it turns out that this is just a transformation. But this is completely different if you are actually saving 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 it has different characteristics, in terms of performance, 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; }
Results are subsequently returned as something similar to:
Count: 00:00:02.4742007 CountWithNew: 00:00:02.4272702 CountSaved: 00:00:01.9810367
This is almost a 20% difference when using saved delegation or creating a new one.
Now itβs obvious that not every program is going to add and remove that many delegates in such a small amount of time, but if you write library classes β classes that can be used in ways you cannot predict β then you really want to have this difference in mind if you ever need to add and remove events (and I wrote a lot of code that does it personally).
So the conclusion is that the entry SomeEvent += new EventHandler(NamedMethod) compiles to the same thing as SomeEvent += NamedMethod . But if you plan to remove this event handler later, you really need to save the delegate. Despite the fact that the Delegate class has special code that allows you to remove a different delegate from the one you added, it must do a non-trivial amount of work to remove this.
If you are not going to save the delegate, then it does not matter - the compiler still creates a new delegate.