Quali sono le differenze tra delegati e eventi? Entrambi non contengono riferimenti a funzioni che possono essere eseguite?
Quali sono le differenze tra delegati e eventi? Entrambi non contengono riferimenti a funzioni che possono essere eseguite?
Risposte:
Una dichiarazione di evento aggiunge un livello di astrazione e protezione sull'istanza delegata . Questa protezione impedisce ai client del delegato di reimpostare il delegato e il suo elenco di invocazioni e consente solo di aggiungere o rimuovere target dall'elenco di invocazioni.
Per capire le differenze puoi guardare questi 2 esempi
Esempio con delegati (in questo caso, un'azione - che è 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 usare 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 dei 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 ignorato i comportamenti precedenti solo con uno mancante +
(ho usato =
invece di +=
)
Un altro punto debole è che ogni classe che usa la tua Animal
classe può sollevare RaiseEvent
semplicemente chiamandola animal.RaiseEvent()
.
Per evitare questi punti deboli puoi usare events
in c #.
La tua classe di animali 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:
Appunti:
EventHandler è dichiarato come il seguente delegato:
public delegate void EventHandler (object sender, EventArgs e)
accetta un mittente (di tipo Oggetto) e argomenti evento. Il mittente è nullo se proviene da metodi statici.
Questo esempio, che usa EventHandler<ArgsSpecial>
, può anche essere scritto usando EventHandler
invece.
Fare riferimento qui per la documentazione su EventHandler
RaiseEvent
purché un metodo chiamante abbia accesso a un'istanza animal
nel codice che utilizza l'evento?
animal.Run(this, new ArgsSpecial("Run faster");
?
Oltre alle proprietà sintattiche e operative, c'è anche una differenza semantica.
I delegati sono, concettualmente, modelli di funzione; cioè, esprimono un contratto a cui una funzione deve aderire per essere considerata del "tipo" del delegato.
Gli eventi rappresentano ... beh, gli eventi. Hanno lo scopo di avvisare qualcuno quando succede qualcosa e sì, aderiscono a una definizione delegata 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 sono implementati nello stesso modo (il che non significa che mi piace avere lo stesso codice due volte).
Ecco un altro buon link a cui fare riferimento. http://csharpindepth.com/Articles/Chapter2/Events.aspx
In breve, il take away dell'articolo - Gli eventi sono incapsulamento sui delegati.
Citazione dall'articolo:
Supponiamo che gli eventi non esistessero come concetto in C # /. NET. In che modo un'altra classe si iscriverebbe a un evento? Tre opzioni:
Una variabile di delegato pubblico
Una variabile delegata supportata da una proprietà
Una variabile delegata con i metodi AddXXXHandler e RemoveXXXHandler
L'opzione 1 è chiaramente orribile, per tutti i normali motivi per cui aborriamo le variabili pubbliche.
L'opzione 2 è leggermente migliore, ma consente agli abbonati di sostituirsi efficacemente: sarebbe fin troppo facile scrivere someInstance.MyEvent = eventHandler; che sostituirà qualsiasi gestore di eventi esistente anziché aggiungerne uno nuovo. Inoltre, è ancora necessario scrivere le proprietà.
L'opzione 3 è fondamentalmente ciò che gli eventi ti danno, 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. Sottoscrivere e annullare la sottoscrizione agli eventi è incapsulato senza consentire l'accesso arbitrario all'elenco dei gestori di eventi e le lingue possono semplificare le cose fornendo la sintassi sia per la dichiarazione che per la sottoscrizione.
public Delegate
variabile esporrà "dati", ma per quanto ne so OOP non ha mai menzionato concetti come un Delegate
(non è né un "oggetto" né un "messaggio") e .NET tratta comunque a malapena i delegati come i dati.
AddXXXHandler
metodi con una private Delegate
variabile può essere una buona opzione. In questo caso è possibile verificare se un gestore è già impostato e reagire in modo appropriato. Questa potrebbe anche essere una buona configurazione se hai bisogno dell'oggetto che tiene in mano Delegate
per poter cancellare tutti i gestori ( event
non ti dà alcun modo per farlo).
NOTA: se si ha accesso a C # 5.0 Unleashed , leggere le "Limitazioni sull'uso normale dei delegati" nel capitolo 18 "Eventi" per comprendere meglio le differenze tra i due.
Mi aiuta sempre ad avere un esempio semplice e concreto. Quindi eccone uno per la comunità. In primo luogo, mostro come usare 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 che io abbia un'app WinForms con una singola casella a discesa. Il menu a discesa è associato a un List<Person>
. Dove Persona ha proprietà di ID, Nome, NickName, HairColor. Nel modulo principale è un controllo utente personalizzato che mostra le proprietà di quella persona. Quando qualcuno seleziona una persona nel menu a discesa, le etichette nell'aggiornamento del controllo utente aggiornano per mostrare le proprietà della persona selezionata.
Ecco come funziona. Abbiamo tre file che ci aiutano a mettere insieme questo:
Ecco il codice pertinente 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;
}
}
Finalmente 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. Quindi è così che faresti funzionare tutto questo senza usare eventi e solo usando delegati . Abbiamo appena messo un delegato pubblico in una classe: puoi renderlo statico o un singleton o qualsiasi altra cosa. Grande.
MA, MA, MA, non vogliamo fare ciò che ho appena descritto sopra. Perché i campi pubblici sono dannosi per molte, molte ragioni. Quindi quali sono le nostre opzioni? Come descrive John Skeet, ecco le nostre opzioni:
PersonChangedDel = null
, cancellando tutti gli altri abbonamenti. l'altro problema che rimane qui è che, poiché gli utenti hanno accesso al delegato, possono invocare gli obiettivi nell'elenco di invocazione: non vogliamo che gli utenti esterni abbiano accesso a quando sollevare i nostri eventi.Questa terza opzione è essenzialmente ciò che un evento ci offre. Quando dichiariamo un EventHandler, ci dà accesso a un delegato - non pubblicamente, non come proprietà, ma come questa cosa chiamiamo un evento che ha appena aggiunto / rimosso gli accessor.
Vediamo come appare lo stesso programma, ma ora utilizzo un evento anziché il delegato pubblico (ho anche cambiato il nostro mediatore in un singleton):
Esempio 2: con EventHandler anziché 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 F12 su EventHandler, ti mostrerà che la definizione è solo un delegato generico con l'oggetto "mittente" extra:
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 ed EventArgs come parametro, ho creato questa classe con una sola proprietà al suo interno:
class PersonChangedEventArgs
{
public Person Person { get; set; }
}
Speriamo che questo ti mostri un po 'sul perché abbiamo eventi e su come sono diversi - ma funzionalmente uguali - dei delegati.
The other problem that remains here is that since the users have access to the delegate, they can invoke the targets in the invocation list -- we don't want external users having access to when to raise our events
. Nell'ultima versione di Mediator
, puoi comunque chiamare il OnPersonChange
ogni volta che hai un riferimento al singleton. Forse dovresti menzionare che l' Mediator
approccio non impedisce quel comportamento particolare ed è più vicino a un bus di eventi.
È inoltre possibile utilizzare eventi nelle dichiarazioni di interfaccia, non così per i delegati.
Action a { get; set; }
dentro una definizione di interfaccia.
Che grande incomprensione tra eventi e delegati !!! Un delegato specifica un TIPO (come un class
, o uninterface
fa), mentre un evento è solo un tipo 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, il tipo di evento deve essere specificato da un delegato. Ad esempio, NON PUOI dichiarare un evento di un tipo definito da un'interfaccia.
Concludendo, possiamo fare la seguente Osservazione: il tipo di un 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 delle partizioni da I a VI dell'ECMA-335 (CLI) :
Nell'uso tipico, TypeSpec (se presente) identifica un delegato la cui firma corrisponde agli argomenti passati al metodo di attivazione dell'evento.
Tuttavia, questo fatto NON implica che un evento utilizzi un campo delegato di supporto . In verità, un evento può utilizzare un campo di supporto di qualsiasi diverso tipo di struttura di dati di propria scelta. Se si implementa esplicitamente un evento in C #, si è liberi di scegliere il modo in cui archiviare i gestori di eventi (si noti che i gestori di eventi sono istanze del tipo di evento , che a sua volta è obbligatoriamente un tipo delegato --- dall'Osservazione precedente ). Tuttavia, è possibile archiviare i gestori di eventi (che sono istanze delegate) in una struttura di dati come a List
o a Dictionary
o in qualsiasi altro, o persino in un campo delegato 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, che prevedono entrambi un determinato tipo di delegato. Sia C # che vb.net possono generare automaticamente codice per i metodi di aggiunta e rimozione che definiranno un delegato per contenere le iscrizioni agli eventi e aggiungere / rimuovere i delegati passati a / da quel delegato di sottoscrizione. VB.net genererà anche automaticamente il codice (con l'istruzione RaiseEvent) per invocare l'elenco delle iscrizioni se e solo se non è vuoto; per qualche motivo, C # non genera quest'ultimo.
Si noti che mentre è comune gestire le iscrizioni agli eventi utilizzando un delegato multicast, questo non è l'unico modo per farlo. Da una prospettiva pubblica, un aspirante abbonato deve sapere come far sapere a un oggetto che desidera ricevere eventi, ma non ha bisogno di sapere quale meccanismo utilizzerà l'editore per sollevare gli eventi. Si noti inoltre che, sebbene chiunque abbia definito la struttura dei dati degli eventi in .net apparentemente abbia pensato che ci dovrebbe essere un mezzo pubblico per aumentarli, né C # né vb.net fanno uso di quella funzione.
Per definire l'evento in modo semplice:
L'evento è un RIFERIMENTO a un delegato con due restrizioni
Sopra due sono i punti deboli per i delegati ed è affrontato in caso di necessità. Esempio di codice completo per mostrare la differenza nel violinista è qui https://dotnetfiddle.net/5iR3fB .
Attiva / disattiva il commento tra Evento e Delegato e il codice client che invoca / assegna valori da delegare per comprendere la differenza
Ecco il codice inline.
/*
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();
}
}
Il delegato è un puntatore a funzione sicura. L'evento è un'implementazione del modello di progettazione editore-abbonato che utilizza delegato.
Se controlli Intermediate Language, saprai che il compilatore .net converte delegato in una classe sigillata in IL con alcune funzioni integrate, come invoke, startInvoke, endInvoke e classe delegate ereditata da un'altra classe, forse chiamata "SystemMulticast". Immagino che Event sia una classe figlio di delegato con alcune proprietà aggiuntive.
La differenza tra istanza di evento e delegato è che non è possibile eseguire eventi al di fuori della dichiarazione. Se si dichiara un evento in classe A, è possibile eseguire questo evento solo in classe A. Se si dichiara un delegato in classe A, è possibile utilizzare questo delegato ovunque. Penso che questa sia la principale differenza tra loro