2010-05-01 7 views
61

Duplicar posibles:
C#: Difference between ‘ += anEvent’ and ‘ += new EventHandler(anEvent)’+ = new manejador de sucesos (Método) vs + = Método

Hay dos formas básicas para suscribirse a un evento:

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

¿Cuál es la diferencia, y cuándo debería elegir uno sobre el otro?

Editar: Si es lo mismo, ¿por qué VS por defecto a la versión larga, abarrotando el código? Eso no tiene ningún sentido para mí.

+6

Los generadores de código se amontonan con más frecuencia (utilizando nombres calificados largos en lugar de usar directivas). Su objetivo es la facilidad de generación y evitar errores, no la legibilidad. –

+1

Estaba hablando específicamente sobre el fragmento de código, que no es utilizado por un generador de códigos automatizado. – mafu

+1

Yo también. Las formas largas son menos propensas a crear ambigüedades. –

Respuesta

35

Dado que parecía haber alguna disputa sobre mi respuesta original, decidí hacer algunas pruebas, incluso mirando el código generado y monitoreando el rendimiento.

En primer lugar, aquí es nuestro banco de pruebas, una clase con un delegado y otra clase de consumirlo:

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

primero que debe hacer es mirar a la IL generada:

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

Entonces resulta que, sí, generan un IL idéntico. Estaba equivocado originalmente. Pero eso es no toda la historia. Puede ser que esté fuera de tema aquí, pero creo que es importante incluir esto cuando se habla de eventos y delegados:

Crear y comparar diferentes delegados no es barato.

Cuando escribí esto, estaba pensando que la primera sintaxis podía convertir al grupo de métodos como delegado, pero resulta que solo es una conversión. Pero es completamente diferente cuando realmente guarda el delegado. Si sumamos esto al consumidor:

class Counter 
{ 
    EventHandler savedEvent; 

    public Counter() 
    { 
     savedEvent = CountEvent; 
    } 

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

se puede ver que esto tiene características muy diferentes, en cuanto al rendimiento, de los otros dos:

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

Los resultados consistentemente regresan como algo similar a:

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

Eso es casi un 20% diferencia cuando se utiliza una d salvado elegate vs. crear uno nuevo.

Ahora, obviamente, no todos los programas agregarán y eliminarán tantos delegados en tan poco tiempo, pero si está escribiendo clases de biblioteca, clases que podrían usarse de maneras que no puede predecir, entonces realmente Desea mantener esta diferencia en mente si alguna vez necesita agregar y eliminar los eventos (y he escrito una gran cantidad de código que hace esto, personalmente).

Por lo tanto, la conclusión de esto es escribir SomeEvent += new EventHandler(NamedMethod) compila a la misma cosa que . Pero si planea eliminar ese controlador de eventos más tarde, realmente debería guardar delegado. Aunque la clase Delegate tiene un código de caso especial que le permite eliminar un delegado diferente al que ha agregado, tiene que hacer una cantidad de trabajo no trivial para lograrlo.

Si no va a guardar el delegado, entonces no importa: el compilador termina creando un nuevo delegado de todos modos.

+0

Esto es totalmente incorrecto (y solo parcialmente corregido). No hay diferencia entre las dos formas, ni para '+ =' o '- ='. Son anotaciones diferentes para la misma cosa. –

+0

@Aero, leí las cosas vinculadas, finaliza con una demostración de que las 2 formas son iguales y no prueba que una forma sea más rápida o mejor. –

+2

Y solo para asegurarse de que escribí una pequeña prueba y miré la IL. Las 2 formas producen __identical__ código de IL. –

2

No hay diferencia, la primera es más específica en su definición.

24

No hay diferencia desde el punto de vista de la programación, son equivalentes entre sí. El compilador hará prácticamente lo que has hecho en la primera línea con la segunda línea detrás de escena. Así que siempre optaría por el segundo enfoque (menos código).

Re: Editar

Probablemente porque sienten que es mejor para mostrar a los desarrolladores la adecuada manera de hacer las cosas en lugar de accesos directos. Su suposición es tan buena como la mía :)

7

No hay diferencia. Antes de .NET 2.0, cada asignación de variables debe ser del tipo exacto, los compiladores no infieren demasiado. Para hacer una reparación, VS 2003 emiten new EventHandler alrededor del nombre de la función. Esa es solo mi suposición. Porque ..

Intenté algo ahora en VS 2008, textBox1.KeyDown += (KeyEventHandler)textBox1_KeyDown, que también funciona. Me desconcierta por qué eligen new EventHandler(checkBox1_CheckStateChanged), en lugar de (EventHandler)checkBox1_CheckStateChanged. Pero ...

ya que no tengo VS 2003 en mi caja, no puedo precisar si el enfoque de conversión también podría funcionar en VS 2003. Pero enfadado, intenté eliminar new EventHandler en el nombre de la función cuando utilizado VS 2003 (.NET 1.1), considerando por qué la necesidad de instanciar (new EventHandler) una función, los delegados son sólo un puntero de función bajo el capó, pero no funciona.

Es solo desde .NET 2.0 en adelante que el compilador de C# comenzó a inferir tanto como sea posible.

Este artículo http://blueonionsoftware.com/blog.aspx?p=aed2ae46-7548-4e5f-83c6-95e00c6f3649 apoyó mi memoria de new EventHandler antes de .NET 2.0 compiladores, fue una Obligatoria

[EDIT]

El siguiente artículo va en profundidad sobre la suscripción/eventos unsubcribing, que supuestamente hay una diferencia entre button1.Click += new EventHandler(button1_Click); y button1.Click += button1_Click;, pero por desgracia no se puede ver ninguna diferencia en el nivel de IL aunque :-(

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

+0

No, la versión larga no es una solución, es la sintaxis completa. El otro es solo una abreviatura que el compilador reescribe a la versión larga. –

+0

@Henk Holterman: borré mi respuesta, luego edité mi respuesta en gran medida. No estoy vagamente seguro de que no era obligatorio entonces, así que antes de intentar escribir lo que puedo recordar y recuperar, intenté buscar primero en Google un artículo que respaldara las cosas que recordaba entonces. La forma larga era obligatoria, pero no puedo postular si es una solución alternativa a las compatibilidades explícitas de compilación de tipos de compiladores, entonces, estoy empezando a aprender ildasm ahora :-) No sé qué hace la instrucción de instancia en la IL i Estoy comprobando ahora –

+0

La forma larga era necesaria en .NET 1.1 y antes. C# 2 introdujo la forma abreviada. Junto con métodos anónimos, un paso hacia lambdas. –

8

la segunda forma es azúcar sintáctica introducida en versiones posteriores de C#.la primera línea funcionará en todas las versiones aunque

Cuestiones relacionadas