2008-08-26 18 views

Respuesta

228

Una declaración Evento añade una capa de abstracción y la protección de la delegado ejemplo. Esta protección evita que los clientes del delegado restablezcan el delegado y su lista de invocación, y solo permite agregar o eliminar objetivos de la lista de invocación.

+35

Si por supuesto , esta capa de protección también evita que los "clientes" (código fuera de la clase/estructura definitoria) deinvoking_ el delegado, y de obtener en cualquier forma el objeto delegado "detrás" del evento. –

+2

.... Y protección en la instancia de delegado ** variable ** –

+5

No del todo cierto. Puede declarar un evento sin una instancia de delegado de back-end. En C#, puede implementar un evento de forma explícita y utilizar una estructura de datos back-end diferente de su elección. –

87

Además de las propiedades sintácticas y operacionales, también hay una diferencia semántica.

Los delegados son, conceptualmente, plantillas de funciones; es decir, expresan un contrato al que debe adherirse una función para ser considerado del "tipo" del delegado.

Los eventos representan ... bueno, los eventos. Tienen la intención de alertar a alguien cuando sucede algo y sí, se adhieren a una definición de delegado pero no son lo mismo.

Incluso si fueran exactamente lo mismo (sintácticamente y en el código IL) todavía quedará la diferencia semántica. En general, prefiero tener dos nombres diferentes para dos conceptos diferentes, incluso si se implementan de la misma manera (lo que no significa que me guste tener el mismo código dos veces).

+8

Excelente descripción de los delegados. – Sampson

+0

Entonces, ¿podríamos decir que un evento es un tipo de delegado "especial"? – Pap

7

También puede usar eventos en las declaraciones de interfaz, no así para los delegados.

+2

@surfen La interfaz puede contener eventos, pero no delegados. –

+0

¿Qué quieres decir exactamente? Puedes tener 'Action a {get; conjunto; } 'dentro de una definición de interfaz. –

5

Un evento en .net es una combinación designada de un método Add y un método Remove, ambos esperan algún tipo particular de delegado. Tanto C# como vb.net pueden autogenerar código para los métodos de agregar y eliminar que definirán un delegado para contener las suscripciones de eventos, y agregar/eliminar la delegación pasada en/desde ese delegado de suscripción. VB.net también autogenerará código (con la instrucción RaiseEvent) para invocar la lista de suscripción si y solo si no está vacía; por alguna razón, C# no genera el último.

Tenga en cuenta que si bien es común administrar suscripciones de eventos utilizando un delegado de multidifusión, ese no es el único medio de hacerlo. Desde una perspectiva pública, un posible suscriptor del evento necesita saber cómo hacer que un objeto sepa que desea recibir eventos, pero no necesita saber qué mecanismo utilizará el editor para generar los eventos. Tenga en cuenta también que, si bien quien definió la estructura de datos de eventos en .net aparentemente pensó que debería haber un medio público para elevarlos, ni C# ni vb.net hacen uso de esa característica.

35

Es un antiguo puesto, pero si uno se topa con él, como lo hice - aquí es otro buen enlace para referirse a .. http://csharpindepth.com/Articles/Chapter2/Events.aspx

brevemente, la quita del artículo - Los eventos son encapsulación sobre delegados. Presupuesto de artículo -..

"eventos Supongamos que no existía como concepto en C#/.NET Cómo sería otra clase suscribirse a un evento?

tres opciones:

  1. delegatevariable pública

  2. variables delegado respaldado por una propiedad

  3. variable de delegado con métodos AddXXXHandler y RemoveXXXHandler

La opción 1 es claramente horrible, fo r todas las razones normales aborrecemos variables públicas.

La opción 2 es mejor, pero permite a los suscriptores anular efectivamente entre sí - sería demasiado fácil escribir algunosInstance.MyEvent = eventHandler; que reemplazaría a cualquier controlador de eventos existente en lugar de agregar uno nuevo en lugar de . Además, aún necesita escribir las propiedades .

opción 3 es básicamente lo que los eventos que dan, pero con un convención garantizada (generado por el compilador y el respaldo de banderas adicionales en el IL) y una aplicación "libre" si están contentos con la semántica que los eventos tipo campo te dan. La suscripción y darse de baja de eventos se encapsula sin permitir el acceso arbitrario a la lista de controladores de eventos, y los idiomas pueden hacer las cosas más simples, proporcionando sintaxis tanto para la declaración y de la suscripción."

+0

Explicación agradable y concisa. Gracias – Pap

69

para entender las diferencias que puedas mira esto 2 ejemplos

Ejemplo con delegados (en este caso, una acción - que es una especie de delegado que no devuelve un valor)

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

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

Para nosotros e el delegado, debe hacer algo como esto:

Animal 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, he anulado los comportamientos anteriores sólo con una falta + (He utilizado = en lugar de +=)

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

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

Su clase de animal cambiará de este modo:

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

    public string Operation {get; set;} 
} 

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

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

para llamar eventos

Animal 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 (utilizando eventos, el compilador protege sus campos del acceso no deseado)
  2. Los eventos no se pueden asignar directamente. En este caso, no dará lugar al error anterior que he mostrado al anular el comportamiento.
  3. Nadie fuera de su clase puede plantear el evento.
  4. Los eventos pueden ser incluidos en una declaración de interfaz, mientras que un campo no puede

Notas:

manejador de sucesos se declara como el siguiente delegado:

public delegate void EventHandler (object sender, EventArgs e) 

que se necesita un emisor (de objetos tipo) y argumentos de evento. El remitente es nulo si proviene de métodos estáticos.

Este ejemplo, que usa EventHandler<ArgsSpecial>, también se puede escribir usando EventHandler.

Consulte here para la documentación sobre el manejador de sucesos

+3

Todo parecía genial hasta que me encontré con "Nadie fuera de tu clase puede plantear el evento". Qué significa eso? ¿Nadie puede llamar a 'RaiseEvent' siempre que un método de llamada tenga acceso a una instancia de' animal' en el código que usa event? – Sung

+6

@Sung Los eventos solo pueden surgir desde dentro de la clase, tal vez no he sido claro al explicar eso. Con los eventos puede llamar a la función que plantea el evento (encapsulación), pero solo se puede subir desde el interior de la clase que la define. Avísame si no estoy seguro. – faby

+1

¡Gran respuesta, amigo! Continúa con el buen trabajo :-) –

6

Qué gran malentendido entre eventos y delegados !!! Un delegado especifica un TIPO (como un class, o un interface), mientras que un evento es solo un tipo de MIEMBRO (como campos, propiedades, etc.). Y, al igual que cualquier otro tipo de miembro, un evento también tiene un tipo. Sin embargo, en el caso de un evento, el tipo de evento debe ser especificado por un delegado. Por ejemplo, NO PUEDE declarar un evento de un tipo definido por una interfaz.

En conclusión, podemos hacer lo siguiente Observación: el tipo de un evento DEBE estar definido por un delegado. Esta es la relación principal entre un evento y un delegado y se describe en la sección II.18 Definición de eventos de ECMA-335 (CLI) Partitions I to VI:

En el uso típico, el TypeSpec (si está presente) identifica un delegado cuya firma coincide con los argumentos pasados ​​al método de fuego del evento.

Sin embargo, este hecho no implica que un evento utiliza un campo delegado respaldo. En verdad, un evento puede usar un campo de respaldo de cualquier tipo diferente de estructura de datos de su elección.Si implementa un evento de manera explícita en C#, usted es libre de elegir la forma en que almacena los controladores de eventos (tenga en cuenta que controladores de eventos son instancias del tipo del evento, que a su vez, es obligatoriamente un tipo delegado --- de la anterior Observación). Pero puede almacenar esos controladores de eventos (que son instancias de delegado) en una estructura de datos como List o Dictionary o cualquier otro, o incluso en un campo de delegado de respaldo. Pero no olvides que NO es obligatorio que utilices un campo de delegado.

6

NOTA: Si tiene acceso a C# 5.0 Unleashed, lea "Limitaciones en el uso normal de delegados" en el Capítulo 18 titulado "Eventos" para comprender mejor las diferencias entre los dos.


Siempre me ayuda a tener un ejemplo simple y concreto. Así que aquí hay uno para la comunidad. Primero le muestro cómo puede usar delegados solos para hacer lo que los Eventos hacen por nosotros. Luego muestro cómo funcionaría la misma solución con una instancia de EventHandler. Y luego explico por qué NO queremos hacer lo que explico en el primer ejemplo. Esta publicación fue inspirada por an article por John Skeet.

Ejemplo 1: Uso delegado pública

Supongamos que tengo una aplicación de Windows Forms con un solo cuadro desplegable. El menú desplegable está vinculado a List<Person>. Donde la persona tiene propiedades de Id, Name, NickName, HairColor. En el formulario principal hay un control de usuario personalizado que muestra las propiedades de esa persona. Cuando alguien selecciona a una persona en el menú desplegable, las etiquetas en la actualización del control de usuario muestran las propiedades de la persona seleccionada.

enter image description here

Aquí es cómo funciona. Tenemos tres archivos que nos ayudan a armar esto:

  • Mediator.cs - clase estática mantiene los delegados
  • Form1.cs - Formulario principal
  • DetailView.cs - control de usuario muestra todos los detalles

Este es el código correspondiente para cada una de las clases:

class Mediator 
{ 
    public delegate void PersonChangedDelegate(Person p); //delegate type definition 
    public static PersonChangedDelegate PersonChangedDel; //delegate instance. Detail view will "subscribe" to this. 
    public static void OnPersonChanged(Person p) //Form1 will call this when the drop-down changes. 
    { 
     if (PersonChangedDel != null) 
     { 
      PersonChangedDel(p); 
     } 
    } 
} 

aquí está nuestro control de usuario:

public partial class DetailView : UserControl 
{ 
    public DetailView() 
    { 
     InitializeComponent(); 
     Mediator.PersonChangedDel += DetailView_PersonChanged; 
    } 

    void DetailView_PersonChanged(Person p) 
    { 
     BindData(p); 
    } 

    public void BindData(Person p) 
    { 
     lblPersonHairColor.Text = p.HairColor; 
     lblPersonId.Text = p.IdPerson.ToString(); 
     lblPersonName.Text = p.Name; 
     lblPersonNickName.Text = p.NickName; 

    } 
} 

Finalmente tenemos el siguiente código en nuestro Form1.cs. Aquí estamos llamando a OnPersonChanged, que llama a cualquier código suscrito al delegado.

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) 
{ 
    Mediator.OnPersonChanged((Person)comboBox1.SelectedItem); //Call the mediator's OnPersonChanged method. This will in turn call all the methods assigned (i.e. subscribed to) to the delegate -- in this case `DetailView_PersonChanged`. 
} 

Ok. Así es como obtendría esto trabajando sin usar los eventos y simplemente usando delegados. Simplemente colocamos a un delegado público en una clase, puede hacerlo estático o un singleton, o lo que sea. Estupendo.

PERO, PERO, PERO, no queremos hacer lo que acabo de describir arriba. Porque public fields are bad por muchas, muchas razones. ¿Entonces, cuales son nuestras opciones?Como John Skeet describe, aquí están nuestras opciones:

  1. Una variable delegado pública (.. Esto es lo que hemos hecho anteriormente, no hagas esto te acabo de decir por encima de eso que es malo)
  2. Ponga el delegado en una propiedad con un get/set (el problema aquí es que los suscriptores podrían anularse mutuamente) para que podamos suscribir un montón de métodos al delegado y luego podríamos accidentalmente decir PersonChangedDel = null, eliminando todas las demás suscripciones. El otro problema lo que queda aquí es que, dado que los usuarios tienen acceso al delegado, pueden invocar los objetivos en la lista de invocación; no queremos que los usuarios externos tengan acceso a cuándo aumentar nuestros eventos.
  3. Una variable de delegado con los métodos AddXXXHandler y RemoveXXXHandler

Esta tercera opción es esencialmente lo que un evento nos brinda. Cuando declaramos un EventHandler, nos da acceso a un delegado, no públicamente, no como una propiedad, sino que a esto lo llamamos un evento que acaba de agregar/eliminar accesadores.

Vamos a ver lo que el mismo programa se parece, pero ahora usando un evento en lugar del delegado pública (También he cambiado nuestro Mediador a un conjunto unitario):

Ejemplo 2: Con manejador de sucesos en lugar de un delegado pública

Mediador:

class Mediator 
{ 

    private static readonly Mediator _Instance = new Mediator(); 

    private Mediator() { } 

    public static Mediator GetInstance() 
    { 
     return _Instance; 
    } 

    public event EventHandler<PersonChangedEventArgs> PersonChanged; //this is just a property we expose to add items to the delegate. 

    public void OnPersonChanged(object sender, Person p) 
    { 
     var personChangedDelegate = PersonChanged as EventHandler<PersonChangedEventArgs>; 
     if (personChangedDelegate != null) 
     { 
      personChangedDelegate(sender, new PersonChangedEventArgs() { Person = p }); 
     } 
    } 
} 

Tenga en cuenta que si F12 en el manejador de sucesos, se mostrará que la definición es sólo un delegado genérico-cado con el "emisor" objeto adicional:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e); 

el control de usuario:

public partial class DetailView : UserControl 
{ 
    public DetailView() 
    { 
     InitializeComponent(); 
     Mediator.GetInstance().PersonChanged += DetailView_PersonChanged; 
    } 

    void DetailView_PersonChanged(object sender, PersonChangedEventArgs e) 
    { 
     BindData(e.Person); 
    } 

    public void BindData(Person p) 
    { 
     lblPersonHairColor.Text = p.HairColor; 
     lblPersonId.Text = p.IdPerson.ToString(); 
     lblPersonName.Text = p.Name; 
     lblPersonNickName.Text = p.NickName; 

    } 
} 

Por último, aquí está el código de Form1.cs:

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) 
{ 
     Mediator.GetInstance().OnPersonChanged(this, (Person)comboBox1.SelectedItem); 
} 

Debido a que el manejador de sucesos quiere y EventArgs como un parámetro, creé esta clase con un único bienes que lo integran:

class PersonChangedEventArgs 
{ 
    public Person Person { get; set; } 
} 

Esperemos que muestra un poco acerca de por qué tenemos eventos y cómo son diferentes, pero funcionalmente iguales, como delegados.

+0

Aunque aprecio todo el buen trabajo en este post y disfruté leyendo la mayor parte, todavía siento que un problema no se aborda: 'El otro problema que queda aquí es que, dado que los usuarios tienen acceso al delegado, pueden invocar los objetivos en la invocación lista: no queremos que los usuarios externos tengan acceso a cuándo organizar nuestros eventos. En la última versión del 'Mediator', aún puede llamar al' OnPersonChange' cada vez que tenga una referencia al singleton. mencionar tha t el enfoque 'Mediator' no impide ese comportamiento en particular, y está más cerca de un bus de eventos. –

3

Para definir sobre el evento en forma sencilla:

de eventos es una referencia a un delegado con dos restricciones

  1. no puede ser invocado directamente
  2. valores no se pueden asignar directamente (por ejemplo, eventObj = método de delegado)

anterior t wo son los puntos débiles para los delegados y se aborda en el evento. Ejemplo de código completo para mostrar la diferencia en el violín está aquí https://dotnetfiddle.net/5iR3fB.

Activar el comentario entre el evento y el Delegado y el código de cliente que invoca/asignar valores a delegar a entender la diferencia

Aquí está el código en línea.

/* 
This is working program in Visual Studio. It is not running in fiddler because of infinite loop in code. 
This code demonstrates the difference between event and delegate 
     Event is an delegate reference with two restrictions for increased protection 

      1. Cannot be invoked directly 
      2. Cannot assign value to delegate reference directly 

Toggle between Event vs Delegate in the code by commenting/un commenting the relevant lines 
*/ 

public class RoomTemperatureController 
{ 
    private int _roomTemperature = 25;//Default/Starting room Temperature 
    private bool _isAirConditionTurnedOn = false;//Default AC is Off 
    private bool _isHeatTurnedOn = false;//Default Heat is Off 
    private bool _tempSimulator = false; 
    public delegate void OnRoomTemperatureChange(int roomTemperature); //OnRoomTemperatureChange is a type of Delegate (Check next line for proof) 
    // public OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), 
    public event OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), 

    public RoomTemperatureController() 
    { 
     WhenRoomTemperatureChange += InternalRoomTemperatuerHandler; 
    } 
    private void InternalRoomTemperatuerHandler(int roomTemp) 
    { 
     System.Console.WriteLine("Internal Room Temperature Handler - Mandatory to handle/ Should not be removed by external consumer of ths class: Note, if it is delegate this can be removed, if event cannot be removed"); 
    } 

    //User cannot directly asign values to delegate (e.g. roomTempControllerObj.OnRoomTemperatureChange = delegateMethod (System will throw error) 
    public bool TurnRoomTeperatureSimulator 
    { 
     set 
     { 
      _tempSimulator = value; 
      if (value) 
      { 
       SimulateRoomTemperature(); //Turn on Simulator    
      } 
     } 
     get { return _tempSimulator; } 
    } 
    public void TurnAirCondition(bool val) 
    { 
     _isAirConditionTurnedOn = val; 
     _isHeatTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary) 
     System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn); 
     System.Console.WriteLine("Heat :" + _isHeatTurnedOn); 

    } 
    public void TurnHeat(bool val) 
    { 
     _isHeatTurnedOn = val; 
     _isAirConditionTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary) 
     System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn); 
     System.Console.WriteLine("Heat :" + _isHeatTurnedOn); 

    } 

    public async void SimulateRoomTemperature() 
    { 
     while (_tempSimulator) 
     { 
      if (_isAirConditionTurnedOn) 
       _roomTemperature--;//Decrease Room Temperature if AC is turned On 
      if (_isHeatTurnedOn) 
       _roomTemperature++;//Decrease Room Temperature if AC is turned On 
      System.Console.WriteLine("Temperature :" + _roomTemperature); 
      if (WhenRoomTemperatureChange != null) 
       WhenRoomTemperatureChange(_roomTemperature); 
      System.Threading.Thread.Sleep(500);//Every second Temperature changes based on AC/Heat Status 
     } 
    } 

} 

public class MySweetHome 
{ 
    RoomTemperatureController roomController = null; 
    public MySweetHome() 
    { 
     roomController = new RoomTemperatureController(); 
     roomController.WhenRoomTemperatureChange += TurnHeatOrACBasedOnTemp; 
     //roomController.WhenRoomTemperatureChange = null; //Setting NULL to delegate reference is possible where as for Event it is not possible. 
     //roomController.WhenRoomTemperatureChange.DynamicInvoke();//Dynamic Invoke is possible for Delgate and not possible with Event 
     roomController.SimulateRoomTemperature(); 
     System.Threading.Thread.Sleep(5000); 
     roomController.TurnAirCondition (true); 
     roomController.TurnRoomTeperatureSimulator = true; 

    } 
    public void TurnHeatOrACBasedOnTemp(int temp) 
    { 
     if (temp >= 30) 
      roomController.TurnAirCondition(true); 
     if (temp <= 15) 
      roomController.TurnHeat(true); 

    } 
    public static void Main(string []args) 
    { 
     MySweetHome home = new MySweetHome(); 
    } 


} 
Cuestiones relacionadas