Comme ma réponse initiale semblait être contestée, j'ai décidé de procéder à quelques tests, notamment en examinant le code généré. y le suivi des performances.
Tout d'abord, voici notre banc d'essai, une classe avec un délégué et une autre classe pour le consommer :
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++;
}
}
La première chose à faire est de regarder l'IL généré :
.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 s'avère donc que, oui, ils génèrent des VA identiques. J'avais tort à l'origine. Mais c'est pas toute l'histoire . Je m'éloigne peut-être du sujet, mais je pense qu'il est important d'inclure cet élément lorsque l'on parle d'événements et de délégués :
La création et la comparaison de différents délégués ne sont pas bon marché.
Lorsque j'ai écrit ceci, je pensais que la première syntaxe permettait de couler le groupe de méthodes comme un délégué, mais il s'avère que c'est juste une conversion. Mais c'est complètement différent quand on fait sauver le délégué. Si nous ajoutons ceci au consommateur :
class Counter
{
EventHandler savedEvent;
public Counter()
{
savedEvent = CountEvent;
}
public void CountSaved()
{
producer.EventRaised += savedEvent;
producer.Raise();
producer.EventRaised -= savedEvent;
}
}
Vous pouvez voir que cela a très des caractéristiques différentes, en termes de performances, des deux autres :
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;
}
Les résultats reviennent systématiquement comme quelque chose de similaire :
Count: 00:00:02.4742007
CountWithNew: 00:00:02.4272702
CountSaved: 00:00:01.9810367
C'est presque un 20% différence entre l'utilisation d'un délégué sauvegardé et la création d'un nouveau délégué.
Maintenant, il est évident que tous les programmes ne vont pas ajouter et supprimer autant de délégués en si peu de temps, mais si vous écrivez des classes de bibliothèque - des classes qui peuvent être utilisées de manière imprévisible - alors vous voulez vraiment garder cette différence à l'esprit si vous devez ajouter et retirer (et j'ai écrit beaucoup de code qui fait cela, personnellement).
Donc la conclusion de ceci est, qu'écrire SomeEvent += new EventHandler(NamedMethod)
compile la même chose que juste SomeEvent += NamedMethod
. Mais si vous prévoyez de supprimer ce gestionnaire d'événement plus tard, vous devriez vraiment sauver le délégué . Même si le Delegate
possède un code spécial qui vous permet de supprimer un délégué différent de celui que vous avez ajouté, il doit effectuer une quantité de travail non triviale pour y parvenir.
Si vous n'avez pas l'intention de sauvegarder le délégué, alors cela ne fait aucune différence - le compilateur finit par créer un nouveau délégué de toute façon.