¿Cuáles son las diferencias entre delegados y eventos?


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

Author: 5StringRyan, 2008-08-27

10 answers

Una declaración Event añade una capa de abstracción y protección a la instancia delegate. Esta protección impide que los clientes del delegado restablezcan el delegado y su lista de invocaciones y solo permite agregar o eliminar destinos de la lista de invocaciones.

 249
Author: mmcdole,
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
2008-08-26 23:16:18

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

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

Los eventos representan ... bueno, eventos. Están destinados a alertar a alguien cuando algo sucede 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 gusta tener el mismo código dos veces).

 90
Author: Jorge Córdoba,
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-09-26 20:14:56

Para entender las diferencias puedes ver 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, he anulado los comportamientos anteriores solo con una falta + (He usado = 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
{
    // 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 a 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á usando una propiedad pública, sino un campo público (usando eventos, el compilador protege sus campos de campos no deseados acceso)
  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 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 proviene de métodos estáticos.

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

Consulte aquí para la documentación sobre EventHandler

 80
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
2017-01-19 10:44:08

Es un post antiguo, pero si alguien se tropieza con él, como lo hice yo, aquí hay otro buen enlace al que referirse.. http://csharpindepth.com/Articles/Chapter2/Events.aspx

Brevemente, los eventos de take away from the article son encapsulación sobre delegados. Cita del artículo -

" Supongamos que los eventos no existieran como un concepto en C#/. NET. otra clase ¿suscribirse a un evento?

Tres opciones:

  1. Público delegatevariable

  2. Variable delegada respaldada por una propiedad

  3. Delegar variable con los métodos AddXXXHandler y RemoveXXXHandler

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

La opción 2 es mejor, pero permite a los suscriptores anular efectivamente el uno al otro-sería demasiado fácil de escribir someInstance.MyEvent = EventHandler; que reemplazaría cualquier evento existente manipuladores más bien que añadir uno nuevo. Además, todavía necesita escribir el propiedad.

La opción 3 es básicamente lo que te dan los eventos, pero con una garantía convención (generada por el compilador y respaldada por banderas adicionales en el IL) y una implementación" gratuita " si está satisfecho con la semántica que los eventos de campo te dan. Suscripción y cancelación de la suscripción events se encapsula sin permitir el acceso arbitrario a la lista de manejadores de eventos e idiomas puede hacer las cosas más simples proporcionando sintaxis tanto para la declaración como para la suscripción."

 35
Author: vibhu,
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-12 19:09:08

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

 6
Author: Paul Hill,
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-08-25 14:37:21

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


Siempre me ayuda tener un ejemplo simple y concreto. Así que aquí hay uno para la comunidad. Primero les muestro cómo pueden usar a los 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. Este post fue inspirado por un artículo de John Skeet.

Ejemplo 1: Usando public delegate

Supongamos que tengo una aplicación WinForms con un solo cuadro desplegable. El menú desplegable está vinculado a un List<Person>. Donde la Persona tiene propiedades de Id, Nombre, Apodo, de Tinte. 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 el control de usuario se actualizan para mostrar propiedades de la persona seleccionada.

introduzca la descripción de la imagen aquí

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

  • Mediador.cs class la clase estática contiene los delegados
  • Formulario 1.cs form main form
  • DetailView.cs control user control 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`.
}

Ok. Así es como conseguirías que esto funcione sin usar eventos y solo usando delegados. Ponemos un delegado público en una clase you puedes hacerlo estático o un singleton, o lo que sea. Gran.

PERO, PERO, PERO, no queremos hacer lo que acabo de describir anteriormente. Porque los campos públicos son malos por muchas, muchas razones. Entonces, ¿cuáles son nuestras opciones? Como John Skeet describe, aquí están nuestras opciones:

  1. Una variable delegada pública (esto es lo que acabamos de hacer arriba. no hagas esto. acabo de decir arriba por qué es malo)
  2. Poner el delegado en una propiedad con un get/set (el problema aquí es que los suscriptores podrían sobrescribirse unos a otros so 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 otras suscripciones. El otro problema que queda aquí es que desde el los usuarios tienen acceso al delegado, pueden invocar los destinos en la lista de invocaciones't no queremos que los usuarios externos tengan acceso a cuándo elevar nuestros eventos.
  3. Una variable delegada con métodos AddXXXHandler y RemoveXXXHandler

Esta tercera opción es esencialmente lo que nos da un evento. Cuando declaramos un EventHandler, nos da acceso a un delegado -- no públicamente, no como una propiedad, sino como esta cosa que llamamos un evento que acaba de agregar/quitar accesorios.

Veamos cómo se ve el mismo programa, pero ahora usando un Evento en lugar del delegado público (también he cambiado 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 F12 en el EventHandler, le mostrará que la definición es solo un delegado genérico con el objeto extra "sender":

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 formulario 1.código cs:

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

Debido a que el EventHandler wants y EventArgs como parámetro, he creado esta clase con una sola propiedad:

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

Esperemos que eso les muestre un poco sobre por qué tenemos eventos y cómo son diferentes func pero funcionalmente iguales as como los delegados.

 6
Author: Trevor,
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-05-23 11:33:24

¡Qué gran malentendido entre los eventos y los 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, 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.

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 Definiendo eventos de ECMA-335 (CLI) Particiones I a VI :

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

Sin embargo, este hecho NO implica que un evento utilice un respaldar el campo delegado . En verdad, un evento puede usar un campo de respaldo de cualquier tipo de estructura de datos diferente de su elección. Si implementa un evento explícitamente en C#, 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 de delegado ---de la observación anterior ). Sin embargo, puede almacenar esos controladores de eventos (que son instancias delegadas) en una estructura de datos como un List o un Dictionary o cualquier otra, o incluso en un campo delegado de respaldo. Pero no olvide que NO es obligatorio que utilice un campo delegado.

 6
Author: Miguel Gamboa,
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
2015-09-01 09:43:03

Un evento en.net es una combinación designada de un método Add y un método Remove, los cuales esperan algún tipo particular de delegado. Tanto C# como vb.net puede generar automáticamente código para los métodos agregar y quitar que definirán un delegado para mantener las suscripciones de eventos, y agregar/eliminar el delegado pasado en delegagte a/desde ese delegado de suscripción. VB.net también generará automáticamente código (con la instrucción RaiseEvent) para invocar la lista de suscripción si y solo si no está vacía; para algunos razón, C# no genera este ú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 suscriptor de evento potencial necesita saber cómo hacer saber a un objeto que quiere recibir eventos, pero no necesita saber qué mecanismo utilizará el editor para elevar los eventos. Tenga en cuenta también que mientras que quien definió la estructura de datos de eventos en. net aparentemente pensó que debería haber un medios públicos de criarlos, ni C# ni vb.net hace uso de esa característica.

 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
2011-08-27 17:07:07

Para definir acerca de evento de manera sencilla:

El evento es una REFERENCIA a un delegado con dos restricciones

  1. No se puede invocar directamente
  2. No se pueden asignar valores directamente (p. ej. eventObj = delegateMethod)

Por encima de dos son los puntos débiles para los delegados y se aborda en el evento. Ejemplo de código completo para mostrar la diferencia en fiddler está aquí https://dotnetfiddle.net/5iR3fB .

Alternar el comentario entre Código de evento y Delegado y cliente que invoca/asigna valores a delegar para 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();
    }


}
 3
Author: Venkatesh Muniyandi,
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
2016-09-13 00:09:39

Covariance y Contravariance proporciona 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 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 es especificado como el parámetro de la delegado.
 0
Author: vivek nuna,
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-08-29 06:35:59