Pregunta

¿Cuáles son las diferencias entre delegados y eventos?¿Ambos no contienen referencias a funciones que se pueden ejecutar?

¿Fue útil?

Solución

Un Evento La declaración añade una capa de abstracción y protección a la delegar instancia.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.

Otros consejos

Además de las propiedades sintácticas y operativas, también existe 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 considerada del "tipo" de delegado.

Los eventos representan...bueno, eventos.Su objetivo es 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), seguirá existiendo la diferencia semántica.En general prefiero tener dos nombres diferentes para dos conceptos diferentes, incluso si están implementados de la misma manera (lo que no significa que me guste tener el mismo código dos veces).

Para entender las diferencias puedes mirar estos 2 ejemplos.

Ejemplo con delegados (en este caso, una acción, que es un tipo de delegado que no devuelve un 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:

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, anulé los comportamientos anteriores solo faltando uno + (He usado = en lugar de +=)

Otro punto débil es que cada clase que utiliza tu Animal la clase puede aumentar RaiseEvent solo llamándolo animal.RaiseEvent().

Para evitar estos puntos débiles puedes utilizar events Cª#.

Tu clase Animal cambiará de esta manera:

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ás utilizando una propiedad pública sino un campo público (al usar eventos, el compilador protege tus campos del acceso no deseado)
  2. Los eventos no se pueden asignar directamente.En este caso, no dará lugar al 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.

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

Referirse aquí para obtener documentación sobre EventHandler

Aquí hay otro buen enlace para consultar.http://csharpin Depth.com/Articles/Chapter2/Events.aspx

Brevemente, la conclusión del artículo: los eventos se encapsulan sobre los delegados.

Cita del artículo:

Supongamos que los eventos no existieran como concepto en C#/.NET.¿Cómo se suscribiría otra clase a un evento?Tres opciones:

  1. Una variable delegada pública

  2. Una variable delegada respaldada por una propiedad

  3. Una variable delegada con los métodos AddXXXHandler y RemoveXXXHandler

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

La opción 2 es ligeramente mejor, pero permite a los suscriptores anularse entre sí de manera efectiva; sería muy fácil escribir someInstance.MyEvent = eventHandler;lo que reemplazaría cualquier controlador de eventos existente en lugar de agregar uno nuevo.Además, todavía necesitas escribir las propiedades.

La opción 3 es básicamente lo que le brindan los eventos, pero con una convención garantizada (generada por el compilador y respaldada por indicadores adicionales en IL) y una implementación "gratuita" si está satisfecho con la semántica que le brindan los eventos tipo campo.La suscripción y cancelación de la suscripción a eventos está encapsulada sin permitir acceso arbitrario a la lista de controladores de eventos, y los lenguajes pueden simplificar las cosas al proporcionar sintaxis tanto para la declaración como para la suscripción.

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


Siempre me ayuda tener un ejemplo sencillo y concreto.Así que aquí hay uno para la comunidad.Primero, muestro cómo puedes 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 un artículo por John Skeet.

Ejemplo 1:Usando delegado público

Supongamos que tengo una aplicación WinForms con un único cuadro desplegable.El menú desplegable está vinculado a un List<Person>.Donde Persona tiene propiedades de Id, Nombre, NickName, HairColor.En el formulario principal hay un control de usuario personalizado que muestra las propiedades de esa persona.Cuando alguien selecciona una persona en el menú desplegable, las etiquetas en el control de usuario se actualizan para mostrar las propiedades de la persona seleccionada.

enter image description here

Así es como funciona.Tenemos tres archivos que nos ayudan a armar esto:

  • Mediator.cs: la clase estática contiene a los delegados
  • Form1.cs - formulario principal
  • DetailView.cs: el control de usuario muestra todos los detalles

Aquí está el código relevante 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`.
}

De acuerdo.Así es como harías que esto funcione. sin usar eventos y solo usando delegados.Simplemente ponemos un delegado público en una clase; puedes hacerlo estático o singleton, o lo que sea.Excelente.

PERO, PERO, PERO, no queremos hacer lo que acabo de describir anteriormente.Porque Los campos públicos son malos. por muchas, muchas razones.¿Entonces, cuales son nuestras opciones?Como describe John Skeet, estas son nuestras opciones:

  1. Una variable delegada pública (esto es lo que acabamos de hacer arriba).no hagas esto.Te acabo de decir arriba por qué es malo)
  2. Coloque el delegado en una propiedad con get/set (el problema aquí es que los suscriptores pueden anularse entre sí, por lo que podríamos suscribir un montón de métodos al delegado y luego podríamos decir accidentalmente PersonChangedDel = null, eliminando todas las demás suscripciones.El otro problema 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 generar nuestros eventos.
  3. Una variable delegada con los métodos AddXXXHandler y RemoveXXXHandler

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

Veamos cómo se ve el mismo programa, pero ahora usando un Evento en lugar del delegado público (también cambié nuestro Mediador a un singleton):

Ejemplo 2:Con EventHandler en lugar de un delegado público

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 presiona F12 en EventHandler, le mostrará que la definición es solo un delegado genérico con el objeto "remitente" 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;

    }
}

Finalmente, aquí está el código Form1.cs:

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

Debido a que EventHandler quiere EventArgs como parámetro, creé esta clase con una sola propiedad:

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

Con suerte, esto les mostrará un poco por qué tenemos eventos y en qué se diferencian (pero funcionalmente iguales) como delegados.

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

¡¡¡Qué gran malentendido entre eventos y delegados!!!Un delegado especifica un TIPO (como un class, o un interface lo hace), mientras que un evento es solo un tipo de MIEMBRO (como campos, propiedades, etc.).Y, como 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.

Concluyendo, podemos hacer lo siguiente Observación:el tipo de evento DEBE ser 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) Particiones I a VI:

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

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

Un evento en .net es una combinación designada de un método Agregar y un método Eliminar, los cuales esperan algún tipo particular de delegado.Tanto C# como vb.net pueden generar automáticamente código para los métodos de agregar y eliminar que definirán un delegado para mantener las suscripciones a eventos y agregar/eliminar el delegado pasado a/desde ese delegado de suscripción.VB.net también generará automáticamente código (con la declaración RaiseEvent) para invocar la lista de suscripción si y sólo si no está vacía;por alguna razón, C# no genera este último.

Tenga en cuenta que, si bien es común administrar las suscripciones a eventos mediante un delegado de multidifusión, esa no es la única forma de hacerlo.Desde una perspectiva pública, un posible suscriptor de eventos necesita saber cómo hacer saber a un objeto 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 del evento en .net aparentemente pensó que debería haber un medio público para generarlos, ni C# ni vb.net hacen uso de esa característica.

Para definir sobre evento de manera sencilla:

El evento es un REFERENCIA a un delegado con dos restricciones

  1. No se puede invocar directamente
  2. No se pueden asignar valores directamente (por ejemplo, eventObj = delegadoMethod)

Los dos anteriores son los puntos débiles de los delegados y se abordan en el evento.El ejemplo de código completo para mostrar la diferencia en Fiddler está aquí https://dotnetfiddle.net/5iR3fB .

Cambie el comentario entre Evento y Delegado y el código de cliente que invoca/asigne valores para delegar para comprender 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();
    }


}

Covariance y Contravariance proporcionar flexibilidad adicional a los objetos delegados.Por otro lado, un evento no tiene tales conceptos.

  • Covariance le permite asignar un método al delegado donde el tipo de retorno del método es una clase que se deriva de la clase que especifica el tipo de retorno del delegado.
  • Contravariance le permite asignar un método al delegado donde el tipo de parámetro del método es una clase base de la clase que se especifica como el parámetro del delegado.

Delegado es un puntero de función con seguridad de tipos.El evento es una implementación del patrón de diseño editor-suscriptor que utiliza delegado.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top