Diferencia entre eventos y delegados y sus respectivas aplicaciones [cerrado]

107

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

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

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

halfer
fuente
Sí, entender las diferencias fue realmente difícil, se ven iguales y parecen hacer lo mismo a primera vista
Robert Gould
1
Vea también esta pregunta .
Dimitri C.
1
La diferencia entre dos eventos y delegados es un hecho, no una opinión. La pregunta pide aplicaciones respectivas porque ilustran diferencias en los problemas que resuelven las tecnologías. Esto tampoco es una cuestión de opinión porque nadie preguntó cuál era la mejor. Ninguna parte de esta pregunta es una cuestión de opinión, y esta declaración tampoco es una opinión. En mi opinión. ¿Recibiste tu placa?
Peter Wone

Respuestas:

49

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

Desde una perspectiva semántica, los eventos son acciones provocadas por un objeto cuando se cumplen ciertas condiciones. Por ejemplo, mi clase de Acciones tiene una propiedad llamada Límite y genera un evento cuando los precios de las acciones alcanzan el Límite. Esta notificación se realiza mediante un evento. Si alguien realmente se preocupa por este evento y se suscribe, 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 de multidifusión. Desde una perspectiva semántica, generalmente se utilizan como una especie de entrada. En particular, son una forma 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. Muchos de mis objetos de datos notifican cuando se cumplen ciertas propiedades. El ejemplo más básico, cada vez que cambia una propiedad, se genera un evento PropertyChanged (consulte la interfaz INotifyPropertyChanged). He usado delegados en código para proporcionar diferentes estrategias de convertir ciertos objetos en cadenas. Este ejemplo en particular fue una lista ToString () glorificada de implementaciones para un tipo de objeto particular para mostrarlo a los usuarios.

Szymon Rozga
fuente
4
Tal vez me esté perdiendo algo, pero ¿no es un controlador de eventos un tipo de delegado?
Powerlord
1
Mi respuesta aborda las preguntas Editar # 1 y # 2; diferencias desde una perspectiva de uso. Para los propósitos de esta discusión, son diferentes, aunque, desde un punto de vista técnico, tienes razón. Eche un vistazo a las otras respuestas para ver las diferencias técnicas.
Szymon Rozga
3
"¿Todos los delegados en .Net son delegados de multidifusión"? ¿Incluso delegados que devuelven valores?
Qwertie
5
Si. Para ver el historial, eche un vistazo a msdn.microsoft.com/en-us/magazine/cc301816.aspx . Consulte: msdn.microsoft.com/en-us/library/system.delegate.aspx . Si devuelven valores, el valor que se devuelve es la evaluación del último delegado de la cadena.
Szymon Rozga
los delegados son tipos de referencia que apuntan a los controladores de eventos definidos en la clase de suscriptor. En otras palabras, el delegado se utiliza como enlace entre el evento (en el editor) y el controlador de eventos definido en el suscriptor. En una aplicación, habrá múltiples suscriptores que necesiten escuchar un evento y, en tales escenarios, los delegados nos ofrecen una forma eficiente de vincular al editor y a los suscriptores.
josepainumkal
55

La palabra clave eventes un modificador de alcance para delegados de multidifusión. Las diferencias prácticas entre esto y simplemente declarar un delegado de multidifusión son las siguientes:

  • Puede utilizar eventen una interfaz.
  • El acceso de invocación al delegado de multidifusión 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 (p public event. Ej .).

Como cuestión de interés, puede aplicar +y -a los delegados de multidifusión, 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;

Ejemplo dos, que ilustra 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 nulo para eliminar todos los controladores.

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

Como las propiedades, los eventos tienen una sintaxis completa que nadie usa. Esta:

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 de agregar y quitar son más notorios en la sintaxis bastante rígida que usa VB.NET (sin sobrecargas de operadores).

Peter Wone
fuente
6
+ para "El acceso de invocación al delegado de multidifusión está limitado a la clase declarante", para mí ese es el punto clave de diferencia entre delegados y eventos.
RichardOD
2
Otra diferencia importante (mencionada por itowlson a continuación) es que no se puede cancelar la suscripción de todos los controladores de eventos asignándoles un evento, pero podrían hacerlo con un delegado. (Por cierto, la tuya fue la respuesta más útil para mí de todas estas).
Roman Starkov
4
Por muy útiles que puedan ser Google y stackoverflow, todo esto y más está disponible con detalles asombrosos en la especificación del lenguaje C #, disponible públicamente sin cargo de Microsoft. Sé que a primera vista, dios creó el manual y Jon Skeet se lo tragó, pero hay otras copias :)
Peter Wone
12

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

La combinación de eventos con interfaces (más azúcar) lo convierte en un bocadillo delicioso. Los delegados y las clases abstractas virtuales puras son mucho menos apetecibles.

Sean
fuente
así lo veo yo también. Quiero una explicación más profunda y dulce :)
13
Demasiada azúcar engorda, sin embargo ... = P
Erik Forbes
5

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

Otra diferencia con 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 a aislar a los suscriptores de eventos: puedo agregar mi controlador a un evento, y usted puede agregar su controlador al mismo evento, y no sobrescribirá accidentalmente mi controlador.

itowlson
fuente
4

Aunque los eventos se implementan normalmente con delegados de multidifusión, no es necesario que se utilicen de esa manera. Si una clase expone un evento, eso significa que la clase expone dos métodos. Sus significados son, en esencia:

  1. Aquí tienes un delegado. Invoquelo cuando suceda algo interesante.
  2. Aquí tienes un delegado. Debe destruir todas las referencias a él tan pronto como sea conveniente (y no volver a llamarlo).

La forma más común para que una clase maneje 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 es necesario que funcionen de esa manera. Desafortunadamente, la arquitectura de eventos no logra 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 guardado 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, con mucho, el enfoque más común.

Super gato
fuente
4

para entender las diferencias puedes mirar 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 había anulado los comportamientos anteriores solo con uno faltante +(he usado en +lugar de +=)

Otro punto débil es que cada clase que usa tu Animalclase puede subir RaiseEventsimplemente llamándola animal.RaiseEvent().

Para evitar estos puntos débiles, puede utilizar eventsen c #.

Tu clase de 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"));
       }
    }

llamar 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á utilizando una propiedad pública sino un campo público (con eventos, el compilador protege sus campos del acceso no deseado)
  2. Los eventos no se pueden asignar directamente. En este caso, no puede hacer el error anterior que mostré al anular el comportamiento.
  3. Nadie fuera de tu clase puede plantear el evento.
  4. Los eventos se pueden incluir en una declaración de interfaz, mientras que un campo no

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 nulo si proviene de métodos estáticos.

También puede usar EventHAndleren su lugar este ejemplo que usaEventHandler<ArgsSpecial>

consulte aquí para obtener documentación sobre EventHandler

faby
fuente
3

Editar # 1 ¿ Cuándo usarías delegados sobre eventos y vsversa? Indique su experiencia en el 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 métodos o constructores de clases:

  • Para que un método pueda implementar un patrón simple de 'método de plantilla' (como, por ejemplo, los delegados Predicatey Actionse pasan a las clases de colección genéricas .Net)
  • O para que la clase pueda hacer una 'devolución de llamada' (normalmente una devolución de llamada a un método de la clase que la creó).

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

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

ChrisW
fuente
2

Aunque no tengo razones técnicas para ello, utilizo eventos en el código de estilo de la interfaz de usuario, en otras palabras, en los niveles superiores del código, y uso delegados para la lógica que se encuentra más profunda en el código. Como digo, podría usar cualquiera de los dos, pero encuentro que este patrón de uso es lógicamente sólido, por lo menos, 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, son ganchos / stubs, 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, una especie de estilo de evento de Javascript / navegador. Sin embargo, cuando tengo un delegado, espero REALMENTE esperar que alguien maneje la tarea del delegado y lanzar una excepción si no se maneja.

Robert Gould
fuente
¿Podría dar más detalles sobre eso ya que también uso pares en la interfaz de usuario? Un buen ejemplo sería suficiente .... gracias
1

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

¡Espero que esto ayude!

Pontus Wittenmark
fuente
2
¡Bienvenido a Stack Overflow! Si bien esto teóricamente puede responder a la pregunta, sería preferible incluir aquí las partes esenciales de la respuesta y proporcionar el enlace como referencia.
GhostCat
1

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

ingrese la descripción de la imagen aquí

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

La clase ConsoleLog está suscribiendo 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 eventos de registro 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");
    }
}
Narottam Goyal
fuente