Diferencia entre eventos y delegados y sus respectivas aplicaciones


No veo ventajas de usar eventos sobre delegados, aparte de ser azúcar sintáctica. Tal vez estoy entendiendo mal, pero parece que el evento es solo un marcador de posición para el delegado.

¿Me explicarías las diferencias y cuándo usar cuáles? ¿Cuáles son las ventajas y desventajas? Nuestro código está fuertemente arraigado con eventos, y quiero llegar al fondo de él.

¿Cuándo usarías delegados sobre eventos y viceversa? Por favor, indique su experiencia en el mundo real con ambos, digamos en el código de producción.

Author: halfer, 2009-02-19

10 answers

Desde el punto de vista técnico, otras respuestas han abordado las diferencias.

Desde una perspectiva semántica, los eventos son acciones planteadas por un objeto cuando se cumplen ciertas condiciones. Por ejemplo, mi clase de acciones tiene una propiedad llamada Límite, y aumenta un evento cuando los precios de las acciones alcanzan el Límite. Esta notificación se realiza a través de un evento. Si alguien realmente se preocupa por este evento y se suscribe a él está más allá de la preocupación de la clase propietaria.

Un delegado es Un término más genérico para describir una construcción similar a un puntero en términos de C/C++. Todos los delegados en. Net son delegados multicast. Desde una perspectiva semántica, generalmente se utilizan como una especie de entrada. En particular, son una manera perfecta de implementar el Patrón de Estrategia . Por ejemplo, si quiero ordenar una Lista de objetos, puedo proporcionar una estrategia de comparación al método para decirle a la implementación cómo comparar dos objetos.

He utilizado los dos métodos en el código de producción. Toneladas de mis objetos de datos notifican cuando se cumplen ciertas propiedades. Ejemplo más básico, cuando una propiedad cambia, se genera un evento PropertyChanged (ver interfaz INotifyPropertyChanged). He utilizado delegados en código para proporcionar diferentes estrategias de convertir ciertos objetos en cadena. Este ejemplo en particular era una lista toString() glorificada de implementaciones para un tipo de objeto en particular para mostrarlo a los usuarios.

 47
Author: Szymon Rozga,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-24 12:20:07

La palabra clave event es un modificador de ámbito para delegados multicast. Las diferencias prácticas entre esto y solo declarar un delegado multicast son las siguientes:

  • Puede usar event en una interfaz.
  • El acceso de invocación al delegado multicast está limitado a la clase declarante. El comportamiento es como si el delegado fuera privado para la invocación. A los efectos de la asignación, el acceso se especifica mediante un modificador de acceso explícito (eg public event).

Como un asunto de interés, puede aplicar + y - a delegados multicast, y esta es la base de la sintaxis += y -= para la asignación combinada de delegados a eventos. Estos tres fragmentos son equivalentes:

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B + C;

Muestra dos, ilustrando tanto la asignación directa como la asignación combinada.

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B;
A += C;

Ejemplo tres: sintaxis más familiar. Probablemente esté familiarizado con la asignación de null para eliminar todos los controladores.

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = null;
A += B;
A += C;

Me gusta propiedades, eventos tener una sintaxis completa que nadie usa nunca. Esto:

class myExample 
{
  internal EventHandler eh;

  public event EventHandler OnSubmit 
  { 
    add 
    {
      eh = Delegate.Combine(eh, value) as EventHandler;
    }
    remove
    {
      eh = Delegate.Remove(eh, value) as EventHandler;
    }
  }

  ...
}

...hace exactamente lo mismo que esto:

class myExample 
{
  public event EventHandler OnSubmit;
}

Los métodos add y remove son más conspicuos en la sintaxis bastante estilizada que VB.NET usos (sin sobrecargas del operador).

 52
Author: Peter Wone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-19 04:14:48

Los eventos son azúcar sintáctica. Son deliciosos. Cuando veo un evento, sé qué hacer. Cuando veo a un delegado, no estoy tan seguro.

Combinar eventos con interfaces (más azúcar) hace que sea un aperitivo delicioso. Los delegados y las clases abstractas virtuales son mucho menos apetecibles.

 13
Author: Sean,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-19 01:29:52

Los eventos se marcan como tales en los metadatos. Esto permite cosas como los formularios de Windows o ASP.NET diseñadores para distinguir eventos de meras propiedades de tipo delegado, y proporcionar el soporte adecuado para ellos (mostrándolos específicamente en la pestaña Eventos de la ventana Propiedades).

Otra diferencia con respecto a una propiedad de tipo delegado es que los usuarios solo pueden agregar y eliminar controladores de eventos, mientras que con una propiedad de tipo delegado pueden establecer el valor:

someObj.SomeCallback = MyCallback;  // okay, replaces any existing callback
someObj.SomeEvent = MyHandler;  // not okay, must use += instead

Esto ayuda para aislar a los suscriptores de eventos: puedo agregar mi controlador a un evento, y puedes agregar tu controlador al mismo evento, y no sobrescribirás accidentalmente mi controlador.

 5
Author: itowlson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-19 01:48:30

Editar#1 ¿Cuándo usarías delegados sobre eventos y vs. versa? Indique su experiencia del mundo real con ambos, digamos en el código de producción.

Cuando diseño mis propias API, defino delegados que se pasan como parámetros a los métodos, o a los constructores de clases:

  • Para que un método pueda implementar un patrón simple de ' método de plantilla '(como, por ejemplo, los delegados Predicate y Action se pasan a la colección genérica. Net clases)
  • O para que la clase pueda hacer un 'callback' (típicamente un callback a un método de la clase que lo creó).

Estos delegados generalmente no son opcionales en tiempo de ejecución (es decir, no deben ser null).

Tiendo a no usar eventos; pero cuando uso eventos, los uso para opcionalmente señalar eventos a cero, uno o más clientes que podrían estar interesados, es decir, cuando tiene sentido que una clase (por ejemplo, la clase System.Windows.Form) existe y se ejecuta si cualquier cliente ha agregado o no un controlador de eventos a su evento (por ejemplo, el evento 'mouse down' del formulario existe, pero es opcional si algún cliente externo está interesado en instalar un controlador de eventos en ese evento).

 4
Author: ChrisW,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-19 02:30:02

Aunque los eventos se implementan típicamente con delegados de multidifusión, no hay ningún requisito de que se utilicen de esta manera. Si una clase expone evento, significa que la clase expone dos métodos. Sus significados son, en esencia:

  1. Aquí hay un delegado. Por favor invócalo cuando algo interesante suceda.
  2. Aquí hay un delegado. Debe destruir toda referencia a ella tan pronto como sea conveniente (y ya no llamarlo).

La forma más común para que una clase manejar un evento que expone es definir un delegado de multidifusión y agregar / eliminar cualquier delegado que se pase a los métodos anteriores, pero no hay ningún requisito de que funcionen de esa manera. Desafortunadamente, la arquitectura de eventos no puede hacer algunas cosas que habrían hecho que los enfoques alternativos fueran mucho más limpios (por ejemplo, hacer que el método de suscripción devuelva un MethodInvoker, que sería mantenido por el suscriptor; para cancelar la suscripción de un evento, simplemente invoque el método devuelto), por lo que los delegados de multidifusión son enfoque más común.

 4
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-07 23:50:47

Para entender las diferencias puedes ver estos 2 ejemplos

Ejemplo con Delegados (Acción en este caso que es un tipo de delegado que no devuelve valor)

public class Animal
{
    public Action Run {get; set;}

    public void RaiseEvent()
    {
        if (Run != null)
        {
            Run();
        }
    }
}

Para usar el delegado debes hacer algo como esto

Animale animal= new Animal();
animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running") ;
animal.RaiseEvent();

Este código funciona bien, pero podría tener algunos puntos débiles.

Por ejemplo, si escribo esto

animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running");
animal.Run = () => Console.WriteLine("I'm sleeping") ;

Con la última línea de código que tenía sobreescribir los comportamientos anteriores solo con una falta + (He utilizado + en lugar de +=)

Otro punto débil es que cada clase que usa su clase Animal puede elevar RaiseEvent simplemente llamándola animal.RaiseEvent().

Para evitar estos puntos débiles puede usar events en c#.

Su clase Animal cambiará de esta manera

public class ArgsSpecial :EventArgs
   {
        public ArgsSpecial (string val)
        {
            Operation=val;
        }

        public string Operation {get; set;}
   } 



 public class Animal
    {
       public event EventHandler<ArgsSpecial> Run = delegate{} //empty delegate. In this way you are sure that value is always != null because no one outside of the class can change it

       public void RaiseEvent()
       {  
          Run(this, new ArgsSpecial("Run faster"));
       }
    }

Para llamar a eventos

 Animale animal= new Animal();
 animal.Run += (sender, e) => Console.WriteLine("I'm running. My value is {0}", e.Operation);
 animal.RaiseEvent();

Diferencias:

  1. No estás usando una propiedad pública sino un campo público (con eventos el compilador protege tus campos de accesos no deseados)
  2. Los eventos no pueden ser asignado directamente. En este caso no se puede hacer el error anterior que he mostrado con sobreescribir el comportamiento.
  3. Nadie fuera de tu clase puede elevar el evento.
  4. Los eventos se pueden incluir en una declaración de interfaz, mientras que un campo no puede

Notas

EventHandler se declara como el siguiente delegado:

public delegate void EventHandler (object sender, EventArgs e)

Toma un remitente (de tipo Objeto) y argumentos de evento. El remitente es null si viene de static método.

Puede usar también EventHAndler en su lugar este ejemplo que usa EventHandler<ArgsSpecial>

Consulte aquí para la documentación sobre EventHandler

 3
Author: faby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-12 14:21:18

Aunque no tengo razones técnicas para ello, uso eventos en código de estilo de interfaz de usuario, en otras palabras, en los niveles más altos del código, y uso delegados para la lógica que se encuentra más profundo en el código. Como digo, podría usar cualquiera de los dos, pero creo que este patrón de uso es lógicamente sólido, si nada más, ayuda a documentar los tipos de devoluciones de llamada y su jerarquía también.


Editar: Creo que la diferencia en los patrones de uso que tengo sería que, me parece perfectamente aceptable ignorar los eventos, ellos son ganchos / talones, si necesita saber sobre el evento, escúchelos, si no le importa el evento simplemente ignórelo. Es por eso que los uso para la interfaz de usuario, tipo de Javascript/estilo de evento del navegador. Sin embargo, cuando tengo un delegado, espero REALMENTE esperar que alguien se encargue de la tarea del delegado, y lanzar una excepción si no se maneja.

 2
Author: Robert Gould,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-20 03:38:30

La diferencia entre eventos y delegados es mucho menor de lo que solía pensar.. Acabo de publicar un video super corto de YouTube sobre el tema: https://www.youtube.com/watch?v=el-kKK-7SBU

Espero que esto ayude!

 1
Author: Pontus Wittenmark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-25 15:02:12

Si usamos solo delegate en lugar de Event, el suscriptor tiene la oportunidad de clonar (), invocar() el propio delegado como se muestra a continuación en la imagen. Lo cual no está bien.

introduzca la descripción de la imagen aquí

Esa es la principal diferencia b/n evento y delegado. el suscriptor solo tiene un derecho, es decir, escuchar los eventos

La clase ConsoleLog se suscribe a eventos de registro a través de EventLogHandler

public class ConsoleLog
{
    public ConsoleLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write on console : " + str);
    }
}

La clase FileLog está suscribiendo el registro eventos a través de EventLogHandler

public class FileLog
{
    public FileLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write in File : " + str);
    }
}

La clase de operación está publicando eventos de registro

public delegate void logDelegate(string str);
public class Operation
{
    public event logDelegate EventLogHandler;
    public Operation()
    {
        new FileLog(this);
        new ConsoleLog(this);
    }

    public void DoWork()
    {
        EventLogHandler.Invoke("somthing is working");
    }
}
 0
Author: Narottam Goyal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-17 18:33:22