martes, 21 de julio de 2009

Diferencias entre Eventos y Delegados - En qué son distintos?

Eventos versus Delegados (Events vs Delegates)

Es la típica pregunta acerca de la diferencia entre eventos y delegados, considerando que uno siempre podría imitar la funcionalidad de un evento utilizando un delegado. Esto considerando que un evento siempre está relacionado a un delegado.

Un evento es parecido a un multicast delegate en algún sentido.

Veamos el siguiente ejemplo donde tenemos msgNotifier (evento) y msgNotifier2 (delegado simple) que aparentan comportarse exactamente igual en todo sentido.

using System;

namespace EventAndDelegate
{
delegate void MsgHandler(string s);

class Class1
{
public static event MsgHandler msgNotifier;
public static MsgHandler msgNotifier2;
[STAThread]
static void Main(string[] args)
{
Class1.msgNotifier += new MsgHandler(PipeNull);
Class1.msgNotifier2 += new MsgHandler(PipeNull);
Class1.msgNotifier("test");
Class1.msgNotifier2("test2");
}

static void PipeNull(string s)
{
return;
}
}
}


Si vemos el código IL para el método Main vemos que ambos msgNotifier y msgNotifier2 son usados de la misma manera.

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 95 (0x5f)
.maxstack 4
IL_0000: ldsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier
IL_0005: ldnull
IL_0006: ldftn void EventAndDelegate.Class1::PipeNull(string)
IL_000c: newobj instance void EventAndDelegate.MsgHandler::.ctor(object,
native int)
IL_0011: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
IL_0016: castclass EventAndDelegate.MsgHandler
IL_001b: stsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier
IL_0020: ldsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier2
IL_0025: ldnull
IL_0026: ldftn void EventAndDelegate.Class1::PipeNull(string)
IL_002c: newobj instance void EventAndDelegate.MsgHandler::.ctor(object,
native int)
IL_0031: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
IL_0036: castclass EventAndDelegate.MsgHandler
IL_003b: stsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier2
IL_0040: ldsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier
IL_0045: ldstr "test"
IL_004a: callvirt instance void EventAndDelegate.MsgHandler::Invoke(string)
IL_004f: ldsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier2
IL_0054: ldstr "test2"
IL_0059: callvirt instance void EventAndDelegate.MsgHandler::Invoke(string)
IL_005e: ret
} // end of method Class1::Main


Si vemos en el MSDN keywords list vemos que los event son solo modificadores (modifier).

La pregunta es que cosa exactamente modifican?

Valor agregado de un evento
Eventos e interfaces

Un evento puede ser incluido en la declaración de una interface mientras que un campo no.

interface ITest
{
event MsgHandler msgNotifier; // compila
MsgHandler msgNotifier2; // error CS0525: Interfaces cannot contain fields
}

class TestClass : ITest
{
public event MsgHandler msgNotifier; // cuando se implementa una interface se debe implementar el evento también.
static void Main(string[] args) {}
}


Invocation de un evento

Un evento solo puede ser invocado en la clase en la que se declaró, mientras que un delegado puede ser invocado desde cualquier parte.

Ejemplo:

using System;

namespace EventAndDelegate
{
delegate void MsgHandler(string s);

class Class1
{
public static event MsgHandler msgNotifier;
public static MsgHandler msgNotifier2;

static void Main(string[] args)
{
new Class2().test();
}
}

class Class2
{
public void test()
{
Class1.msgNotifier("test"); // error CS0070: The event 'EventAndDelegate.Class1.msgNotifier' can only appear on the left hand side of += or -= (except when used from within the type 'EventAndDelegate.Class1')
Class1.msgNotifier2("test2"); // compiles fine
}
}
}

Esta restricción es muy fuerte y aún las clases derivadas no pueden invocar dicho evento (salvo un método protected virtual que lo dispare).

Accesores de Eventos
Eventos disponen de métodos add y remove.
Esto es similar al get y set de las properties.

Firma del evento

el .NET framework agrega una restriction en la firma de los delegados que pueden ser usados como eventos. La firma tienen que ser en este estilo foo(object source, EventArgs e), donde source es el objeto que dispara el evento y e contiene información específica a este evento.

Estas serían a mi entender algunas diferencias entre eventos (events) y delegates (delegados)