Domanda

Quali sono le differenze tra delegati ed eventi?Entrambi non contengono riferimenti a funzioni che possono essere eseguite?

È stato utile?

Soluzione

UN Evento La dichiarazione aggiunge uno strato di astrazione e protezione al file delegare esempio.Questa protezione impedisce ai client del delegato di reimpostare il delegato e il relativo elenco chiamate e consente solo di aggiungere o rimuovere destinazioni dall'elenco chiamate.

Altri suggerimenti

Oltre alle proprietà sintattiche e operative, c'è anche una differenza semantica.

I delegati sono, concettualmente, modelli di funzioni;esprimono cioè un contratto al quale la funzione deve attenersi per essere considerata della “tipologia” del delegato.

Gli eventi rappresentano...beh, eventi.Hanno lo scopo di avvisare qualcuno quando succede qualcosa e sì, aderiscono alla definizione di un delegato ma non sono la stessa cosa.

Anche se fossero esattamente la stessa cosa (sintatticamente e nel codice IL) rimarrebbe comunque la differenza semantica.In generale preferisco avere due nomi diversi per due concetti diversi, anche se implementati nello stesso modo (il che non significa che mi piaccia avere lo stesso codice due volte).

Per capire le differenze puoi guardare questi 2 esempi

Esempio con delegati (in questo caso un'azione, ovvero un tipo di delegato che non restituisce un valore)

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

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

Per utilizzare il delegato, dovresti fare qualcosa del genere:

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

Questo codice funziona bene ma potresti avere alcuni punti deboli.

Ad esempio, se scrivo questo:

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

con l'ultima riga di codice, ho sovrascritto i comportamenti precedenti, mancandone solo uno + (Ho usato = invece di +=)

Un altro punto debole è che ogni classe che utilizza your Animal la classe può aumentare RaiseEvent semplicemente chiamandolo animal.RaiseEvent().

Per evitare questi punti deboli puoi usare events in do#.

La tua classe Animale cambierà in questo 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"));
    }
}

per chiamare eventi

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

Differenze:

  1. Non stai utilizzando una proprietà pubblica ma un campo pubblico (utilizzando gli eventi, il compilatore protegge i tuoi campi da accessi indesiderati)
  2. Gli eventi non possono essere assegnati direttamente.In questo caso, non verrà generato l'errore precedente che ho mostrato sovrascrivendo il comportamento.
  3. Nessuno al di fuori della tua classe può sollevare l'evento.
  4. Gli eventi possono essere inclusi in una dichiarazione di interfaccia, mentre un campo no

Appunti:

EventHandler è dichiarato come il seguente delegato:

public delegate void EventHandler (object sender, EventArgs e)

richiede un mittente (di tipo Oggetto) e argomenti dell'evento.Il mittente è nullo se proviene da metodi statici.

Questo esempio, che utilizza EventHandler<ArgsSpecial>, può anche essere scritto usando EventHandler Invece.

Fare riferimento Qui per la documentazione su EventHandler

Ecco un altro buon collegamento a cui fare riferimento.http://csharpin Depth.com/Articles/Chapter2/Events.aspx

In breve, il punto dell'articolo: gli eventi sono incapsulamenti sui delegati.

Citazione dall'articolo:

Supponiamo che gli eventi non esistessero come concetto in C#/.NET.Come potrebbe un'altra classe iscriversi a un evento?Tre opzioni:

  1. Una variabile delegata pubblica

  2. Una variabile delegata supportata da una proprietà

  3. Una variabile delegata con i metodi AddXXXHandler e RemoveXXXHandler

L’opzione 1 è chiaramente orribile, per tutte le normali ragioni per cui detestiamo le variabili pubbliche.

L'opzione 2 è leggermente migliore, ma consente agli abbonati di sovrascriversi a vicenda in modo efficace: sarebbe fin troppo facile scrivere someInstance.MyEvent = eventHandler;che sostituirebbe qualsiasi gestore di eventi esistente anziché aggiungerne uno nuovo.Inoltre, è ancora necessario scrivere le proprietà.

L'opzione 3 è fondamentalmente ciò che ti danno gli eventi, ma con una convenzione garantita (generata dal compilatore e supportata da flag aggiuntivi nell'IL) e un'implementazione "gratuita" se sei soddisfatto della semantica che ti danno gli eventi simili a campi.La sottoscrizione e l'annullamento della sottoscrizione agli eventi sono incapsulati senza consentire l'accesso arbitrario all'elenco dei gestori di eventi e i linguaggi possono semplificare le cose fornendo la sintassi sia per la dichiarazione che per la sottoscrizione.

NOTA:Se hai accesso a C# 5.0 scatenato, leggere le "Limitazioni al semplice utilizzo dei delegati" nel capitolo 18 intitolato "Eventi" per comprendere meglio le differenze tra i due.


Mi aiuta sempre avere un esempio semplice e concreto.Quindi eccone uno per la comunità.Per prima cosa mostro come utilizzare i delegati da soli per fare ciò che gli eventi fanno per noi.Quindi mostro come funzionerebbe la stessa soluzione con un'istanza di EventHandler.E poi spiego perché NON vogliamo fare quello che spiego nel primo esempio.Questo post è stato ispirato da un articolo di John Skeet.

Esempio 1:Utilizzo del delegato pubblico

Supponiamo di avere un'app WinForms con una singola casella a discesa.Il menu a discesa è associato a un file List<Person>.Dove Persona ha proprietà Id, Nome, NickName, HairColor.Nel modulo principale è presente un controllo utente personalizzato che mostra le proprietà di quella persona.Quando qualcuno seleziona una persona nel menu a discesa, le etichette nel controllo utente si aggiornano per mostrare le proprietà della persona selezionata.

enter image description here

Ecco come funziona.Abbiamo tre file che ci aiutano a mettere insieme il tutto:

  • Mediator.cs: la classe statica contiene i delegati
  • Form1.cs: modulo principale
  • DetailView.cs: il controllo utente mostra tutti i dettagli

Ecco il codice rilevante per ciascuna delle classi:

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);
        }
    }
}

Ecco il nostro controllo utente:

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;

    }
}

Infine abbiamo il seguente codice nel nostro Form1.cs.Qui stiamo chiamando OnPersonChanged, che chiama qualsiasi codice sottoscritto al delegato.

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.Ecco come potresti farlo funzionare senza utilizzare gli eventi E semplicemente usando i delegati.Inseriamo semplicemente un delegato pubblico in una classe: puoi renderlo statico o singleton o altro.Grande.

MA, MA, MA, non vogliamo fare quello che ho appena descritto sopra.Perché i campi pubblici sono pessimi per molte, molte ragioni.Quindi quali sono le nostre opzioni?Come descrive John Skeet, ecco le nostre opzioni:

  1. Una variabile delegata pubblica (questo è ciò che abbiamo appena fatto sopra.non farlo.ti ho appena detto sopra perché è brutto)
  2. Inserisci il delegato in una proprietà con get/set (il problema qui è che gli abbonati potrebbero sovrascriversi a vicenda, quindi potremmo sottoscrivere un gruppo di metodi al delegato e quindi potremmo accidentalmente dire PersonChangedDel = null, cancellando tutti gli altri abbonamenti.L'altro problema che rimane qui è che poiché gli utenti hanno accesso al delegato, possono invocare le destinazioni nell'elenco chiamate: non vogliamo che gli utenti esterni abbiano accesso a quando generare i nostri eventi.
  3. Una variabile delegata con i metodi AddXXXHandler e RemoveXXXHandler

Questa terza opzione è essenzialmente ciò che ci offre un evento.Quando dichiariamo un EventHandler, ci dà accesso a un delegato - non pubblicamente, non come proprietà, ma come questa cosa che chiamiamo evento che ha semplicemente aggiunto/rimosso accessori.

Vediamo come appare lo stesso programma, ma ora utilizzando un Event invece del delegato pubblico (ho anche cambiato il nostro Mediator in un singleton):

Esempio 2:Con EventHandler invece di un delegato pubblico

Mediatore:

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 });
        }
    }
}

Nota che se premi F12 su EventHandler, ti mostrerà che la definizione è solo un delegato generico con l'oggetto "mittente" aggiuntivo:

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

Il controllo utente:

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;

    }
}

Infine, ecco il codice Form1.cs:

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

Poiché EventHandler vuole EventArgs come parametro, ho creato questa classe con una sola proprietà al suo interno:

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

Speriamo che questo vi mostri un po' il motivo per cui organizziamo eventi e come sono diversi, ma funzionalmente uguali, come delegati.

Puoi anche utilizzare gli eventi nelle dichiarazioni dell'interfaccia, non così per i delegati.

Che grande malinteso tra eventi e delegati!!!Un delegato specifica un TIPO (come a class, o un interface fa), mentre un evento è solo una sorta di MEMBRO (come campi, proprietà, ecc.).E, proprio come qualsiasi altro tipo di membro, anche un evento ha un tipo.Tuttavia, nel caso di un evento, la tipologia dell'evento deve essere specificata da un delegato.Ad esempio, NON PUOI dichiarare un evento di un tipo definito da un'interfaccia.

Concludendo possiamo fare quanto segue Osservazione:il tipo di evento DEVE essere definito da un delegato.Questa è la relazione principale tra un evento e un delegato ed è descritta nella sezione II.18 Definizione degli eventi Di ECMA-335 (CLI) Partizioni da I a VI:

Nell'uso tipico, TypeSpec (se presente) identifica un delegato la cui firma corrisponde agli argomenti passati al metodo fire dell'evento.

Tuttavia, questo fatto NON implica che un evento utilizzi un campo delegato di supporto.In verità, un evento può utilizzare un campo sottostante di qualsiasi tipo di struttura dati diverso a tua scelta.Se implementi un evento esplicitamente in C#, sei libero di scegliere il modo in cui archiviarlo gestori di eventi (notare che gestori di eventi sono esempi di tipologia dell'evento, che a sua volta è obbligatoriamente a tipo delegato---dal precedente Osservazione).Tuttavia, puoi archiviare tali gestori eventi (che sono istanze delegate) in una struttura dati come a List o a Dictionary o qualsiasi altro, o anche in un campo di delegati di supporto.Ma non dimenticare che NON è obbligatorio utilizzare un campo delegato.

Un evento in .net è una combinazione designata di un metodo Add e di un metodo Remove, entrambi i quali prevedono un particolare tipo di delegato.Sia C# che vb.net possono generare automaticamente il codice per i metodi di aggiunta e rimozione che definiranno un delegato per contenere le sottoscrizioni agli eventi e aggiungere/rimuovere il delegato passato a/da quel delegato di sottoscrizione.VB.net genererà automaticamente anche il codice (con l'istruzione RaiseEvent) per richiamare l'elenco di sottoscrizione se e solo se non è vuoto;per qualche motivo, C# non genera quest'ultimo.

Tieni presente che, sebbene sia comune gestire le sottoscrizioni agli eventi utilizzando un delegato multicast, questo non è l'unico mezzo per farlo.Da una prospettiva pubblica, un aspirante sottoscrittore di eventi deve sapere come far sapere a un oggetto che desidera ricevere eventi, ma non ha bisogno di sapere quale meccanismo utilizzerà l'editore per generare gli eventi.Si noti inoltre che mentre chiunque abbia definito la struttura dei dati degli eventi in .net apparentemente pensava che dovesse esserci un mezzo pubblico per sollevarli, né C# né vb.net utilizzano tale funzionalità.

Per definire l'evento in modo semplice:

L'evento è a RIFERIMENTO a un delegato con due restrizioni

  1. Non può essere invocato direttamente
  2. Non è possibile assegnare valori direttamente (ad esempio eventObj = delegateMethod)

Sopra due sono i punti deboli per i delegati e vengono affrontati nell'evento.L'esempio di codice completo per mostrare la differenza in Fiddler è qui https://dotnetfiddle.net/5iR3fB .

Alterna il commento tra Evento e Delegato e il codice client che richiama/assegna valori da delegare per comprendere la differenza

Ecco il codice in linea.

 /*
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 E Contravariance fornire ulteriore flessibilità agli oggetti delegati.D'altra parte, un evento non ha tali concetti.

  • Covariance consente di assegnare un metodo al delegato in cui il tipo di restituzione del metodo è una classe che deriva dalla classe che specifica il tipo di restituzione del delegato.
  • Contravariance Consente di assegnare un metodo al delegato in cui il tipo di parametro del metodo è una classe di base della classe specificata come parametro del delegato.

Delegate è un puntatore a funzione indipendente dai tipi.L'evento è un'implementazione del modello di progettazione editore-sottoscrittore che utilizza il delegato.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top