Ответ 1
Поскольку, казалось, возник какой-то спор по моему первоначальному ответу, я решил сделать несколько тестов, включая просмотр сгенерированного кода и мониторинг производительности.
Прежде всего, здесь наш тестовый стенд, класс с делегатом и другой класс, чтобы его использовать:
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
имеет код специального кода, который позволяет удалить референциально другого делегата из того, который вы добавили, он должен выполнить нетривиальное количество работы, чтобы снять это.
Если вы не собираетесь сохранять делегат, то это не имеет значения - компилятор все равно создает новый делегат.