Differenza tra eventi e delegati e le rispettive applicazioni [chiuso]


107

Non vedo vantaggi nell'usare eventi rispetto ai delegati, oltre a essere zucchero sintattico. Forse sto fraintendendo, ma sembra che l'evento sia solo un segnaposto per delegato.

Mi spiegheresti le differenze e quando usarle? quali sono i vantaggi e gli svantaggi? Il nostro codice è fortemente radicato negli eventi e voglio arrivare in fondo.

Quando useresti i delegati sugli eventi e viceversa? Indica la tua esperienza nel mondo reale con entrambi, ad esempio nel codice di produzione.


Sì, avvolgere la mia testa intorno alle differenze è stato davvero difficile, sembrano uguali e sembrano fare lo stesso a prima vista
Robert Gould,

1
Vedi anche questa domanda .
Dimitri C.

1
La differenza tra due eventi e delegati è un dato di fatto, non un'opinione. La domanda richiede le rispettive applicazioni perché illustrano la differenza nei problemi che le tecnologie risolvono. Anche questa non è una questione di opinione perché nessuno ha chiesto quale fosse la migliore. Nessuna parte di questa domanda è una questione di opinione, e anche questa affermazione non è opinione. Secondo me. Hai ricevuto il tuo badge?
Peter ha vinto il

Risposte:


49

Dal punto di vista tecnico, altre risposte hanno affrontato le differenze.

Da una prospettiva semantica, gli eventi sono azioni generate da un oggetto quando vengono soddisfatte determinate condizioni. Ad esempio, la mia classe Stock ha una proprietà chiamata Limit e genera un evento quando i prezzi delle azioni raggiungono il limite. Questa notifica viene effettuata tramite un evento. Se qualcuno si interessa davvero di questo evento e si iscrive ad esso va oltre la preoccupazione della classe del proprietario.

Un delegato è un termine più generico per descrivere un costrutto simile a un puntatore in termini C / C ++. Tutti i delegati in .Net sono delegati multicast. Da una prospettiva semantica, sono generalmente usati come una sorta di input. In particolare, sono un modo perfetto per implementare lo Strategy Pattern . Ad esempio, se desidero ordinare un elenco di oggetti, posso fornire una strategia di confronto al metodo per indicare all'implementazione come confrontare due oggetti.

Ho utilizzato i due metodi nel codice di produzione. Tonnellate dei miei oggetti dati notificano quando vengono soddisfatte determinate proprietà. L'esempio più semplice, ogni volta che una proprietà cambia, viene generato un evento PropertyChanged (vedere l'interfaccia INotifyPropertyChanged). Ho utilizzato i delegati nel codice per fornire diverse strategie per trasformare determinati oggetti in stringhe. Questo particolare esempio era un elenco di implementazioni ToString () glorificato per un particolare tipo di oggetto per visualizzarlo agli utenti.


4
Forse mi manca qualcosa, ma un gestore di eventi non è un tipo di delegato?
Powerlord

1
La mia risposta affronta le domande Modifica n. 1 e n. 2; differenze dal punto di vista dell'utilizzo. Ai fini di questa discussione, sono diversi, anche se, da un punto di vista tecnico, hai ragione. Dai un'occhiata alle altre risposte per le differenze tecniche.
Szymon Rozga

3
"Tutti i delegati in .Net sono delegati multicast"? Anche i delegati che restituiscono valori?
Qwertie

5
Sì. Per la cronologia, dai un'occhiata a msdn.microsoft.com/en-us/magazine/cc301816.aspx . Controlla: msdn.microsoft.com/en-us/library/system.delegate.aspx . Se restituiscono valori, il valore restituito è la valutazione dell'ultimo delegato nella catena.
Szymon Rozga

i delegati sono tipi di riferimento che puntano ai gestori di eventi definiti nella classe del sottoscrittore. In altre parole, il delegato viene utilizzato come collegamento tra l'evento (nell'editore) e il gestore eventi definito nel sottoscrittore. In un'applicazione, ci saranno più abbonati che devono ascoltare un evento e in tali scenari i delegati ci offrono un modo efficiente per collegare editore e abbonati.
josepainumkal

55

La parola chiave eventè un modificatore di ambito per i delegati multicast. Le differenze pratiche tra questo e la semplice dichiarazione di un delegato multicast sono le seguenti:

  • Puoi usare eventin un'interfaccia.
  • L'accesso alla chiamata al delegato multicast è limitato alla classe dichiarante. Il comportamento è come se il delegato fosse privato per la chiamata. Ai fini dell'assegnazione, l'accesso è come specificato da un modificatore di accesso esplicito (ad esempio public event).

È una questione di interesse, è possibile applicare +e -ai delegati multicast, e questa è la base della sintassi +=e -=per l'assegnazione combinata dei delegati agli eventi. Questi tre snippet sono equivalenti:

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B + C;

Esempio due, che illustra sia l'assegnazione diretta che l'assegnazione combinata.

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B;
A += C;

Esempio tre: sintassi più familiare. Probabilmente hai familiarità con l'assegnazione di null per rimuovere tutti i gestori.

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = null;
A += B;
A += C;

Come le proprietà, gli eventi hanno una sintassi completa che nessuno usa mai. Questo:

class myExample 
{
  internal EventHandler eh;

  public event EventHandler OnSubmit 
  { 
    add 
    {
      eh = Delegate.Combine(eh, value) as EventHandler;
    }
    remove
    {
      eh = Delegate.Remove(eh, value) as EventHandler;
    }
  }

  ...
}

... fa esattamente la stessa cosa:

class myExample 
{
  public event EventHandler OnSubmit;
}

I metodi di aggiunta e rimozione sono più evidenti nella sintassi piuttosto elaborata che utilizza VB.NET (senza sovraccarichi di operatori).


6
+ per "L'accesso alla chiamata al delegato multicast è limitato alla classe dichiarante" - questo per me è il punto di differenza fondamentale tra delegati ed eventi.
RichardOD

2
Un'altra importante differenza (menzionata da itowlson di seguito) è che non è possibile annullare l'iscrizione a tutti i gestori di eventi assegnandoli a un evento, ma potrebbero farlo con un delegato. (A proposito, la tua è stata la risposta più utile per me di tutte queste).
Roman Starkov

4
Per quanto utile possa essere Google e stackoverflow, tutto questo e molto altro è disponibile in dettagli sconvolgenti nelle specifiche del linguaggio C #, pubblicamente disponibili gratuitamente da Microsoft. So che a prima vista, Dio ha creato il manuale e Jon Skeet lo ha inghiottito, ma ci sono altre copie :)
Peter Wone

12

Gli eventi sono zucchero sintattico. Sono deliziosi. Quando vedo un evento, so cosa fare. Quando vedo un delegato, non ne sono così sicuro.

Combinare eventi con interfacce (più zucchero) rende uno spuntino appetitoso. I delegati e le classi astratte virtuali pure sono molto meno appetitosi.


è così che la vedo anch'io. Voglio una spiegazione più profonda e dolce :)

13
Tuttavia, troppo zucchero fa ingrassare ... = P
Erik Forbes

5

Gli eventi sono contrassegnati come tali nei metadati. Ciò consente a cose come i Windows Form o le finestre di progettazione ASP.NET di distinguere gli eventi da semplici proprietà di tipo delegato e di fornire loro il supporto appropriato (in particolare mostrandoli nella scheda Eventi della finestra Proprietà).

Un'altra differenza rispetto a una proprietà di tipo delegato è che gli utenti possono solo aggiungere e rimuovere gestori di eventi, mentre con una proprietà di tipo delegato possono impostare il valore:

someObj.SomeCallback = MyCallback;  // okay, replaces any existing callback
someObj.SomeEvent = MyHandler;  // not okay, must use += instead

Questo aiuta a isolare i sottoscrittori dell'evento: posso aggiungere il mio gestore a un evento e tu puoi aggiungere il tuo gestore allo stesso evento e non sovrascriveresti accidentalmente il mio gestore.


4

Sebbene gli eventi siano in genere implementati con delegati multicast, non è necessario che vengano utilizzati in questo modo. Se una classe espone un evento, significa che la classe espone due metodi. I loro significati sono, in sostanza:

  1. Ecco un delegato. Per favore, invocalo quando succede qualcosa di interessante.
  2. Ecco un delegato. Dovresti distruggere ogni riferimento ad esso non appena conveniente (e non chiamarlo più).

Il modo più comune per una classe di gestire un evento che espone è definire un delegato multicast e aggiungere / rimuovere eventuali delegati che vengono passati ai metodi precedenti, ma non è necessario che funzionino in questo modo. Sfortunatamente, l'architettura dell'evento non riesce a fare alcune cose che avrebbero reso gli approcci alternativi molto più puliti (ad esempio, fare in modo che il metodo di sottoscrizione restituisca un MethodInvoker, che verrebbe mantenuto dal sottoscrittore; per annullare l'iscrizione a un evento, invoca semplicemente il metodo restituito) quindi delegati multicast sono di gran lunga l'approccio più comune.


4

per capire le differenze puoi guardare questi 2 esempi

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

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

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

per usare il delegato dovresti fare qualcosa di simile

Animale 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 dovuto sovrascrivere i comportamenti precedenti solo con uno mancante +(ho usato al +posto di +=)

Un altro punto debole è che ogni classe che usa la tua Animalclasse può rilanciare RaiseEventsemplicemente chiamandola animal.RaiseEvent().

Per evitare questi punti deboli puoi usare eventsin 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
    {
       public event EventHandler<ArgsSpecial> Run = delegate{} //empty delegate. In this way you are sure that value is always != null because no one outside of the class can change it

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

per chiamare eventi

 Animale 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 (con eventi il ​​compilatore protegge i tuoi campi da accessi indesiderati)
  2. Gli eventi non possono essere assegnati direttamente. In questo caso non puoi fare l'errore precedente che ho mostrato con l'override del 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 viene dichiarato come il seguente delegato:

public delegate void EventHandler (object sender, EventArgs e)

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

Puoi usare anche EventHAndlerquesto esempio che usiEventHandler<ArgsSpecial>

fare riferimento qui per la documentazione su EventHandler


3

Modifica n. 1 Quando useresti i delegati sugli eventi e rispetto agli altri? Indica la tua esperienza nel mondo reale con entrambi, ad esempio nel codice di produzione.

Quando progetto le mie API, definisco delegati che vengono passati come parametri ai metodi o ai costruttori di classi:

  • In modo che un metodo possa implementare un semplice modello di 'metodo modello' (come ad esempio i delegati Predicatee Actionvengono passati alle classi di raccolta generiche .Net)
  • O così che la classe possa fare un 'callback' (tipicamente un callback a un metodo della classe che lo ha creato).

Questi delegati sono generalmente non facoltativi in fase di esecuzione (cioè non devono esserlo null).

Tendo a non usare gli eventi; ma dove uso gli eventi, li uso per segnalare opzionalmente eventi a zero, uno o più client che potrebbero essere interessati, cioè quando ha senso che una classe (ad esempio la System.Windows.Formclasse) debba esistere ed essere eseguita indipendentemente dal fatto che un client lo abbia ha aggiunto un gestore di eventi al suo evento (ad esempio, l'evento 'mouse down' del modulo esiste, ma è facoltativo se un client esterno è interessato a installare un gestore di eventi su quell'evento).


2

Sebbene non ne abbia ragioni tecniche, utilizzo gli eventi nel codice di stile dell'interfaccia utente, in altre parole, nei livelli più alti del codice e utilizzo i delegati per la logica che risiede più in profondità nel codice. Come ho detto, potresti usare entrambi, ma trovo che questo modello di utilizzo sia logicamente valido, se non altro, aiuta a documentare i tipi di callback e anche la loro gerarchia.


Modifica: penso che la differenza nei modelli di utilizzo che ho sarebbe che, trovo perfettamente accettabile ignorare gli eventi, sono hook / stub, se hai bisogno di sapere dell'evento, ascoltali, se non ti interessa l'evento semplicemente ignoralo. Ecco perché li uso per l'interfaccia utente, un po 'per lo stile degli eventi Javascript / Browser. Tuttavia, quando ho un delegato, mi aspetto REALMENTE che qualcuno gestisca l'attività del delegato e generi un'eccezione se non gestita.


Vorresti approfondire questo aspetto visto che utilizzo anche gli uniformi nell'interfaccia utente? Un buon esempio sarebbe sufficiente .... grazie


1

Se usiamo solo delegate al posto di Event, l'abbonato ha l'opportunità di clone (), invoke () il delegato stesso come mostrato di seguito nell'immagine. Che non va bene.

inserisci qui la descrizione dell'immagine

Questa è la principale differenza tra evento b / n e delegato. l'abbonato ha un solo diritto, cioè ascoltare gli eventi

La classe ConsoleLog sta sottoscrivendo gli eventi di registro tramite EventLogHandler

public class ConsoleLog
{
    public ConsoleLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write on console : " + str);
    }
}

La classe FileLog sta sottoscrivendo gli eventi di registro tramite EventLogHandler

public class FileLog
{
    public FileLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write in File : " + str);
    }
}

La classe operazione sta pubblicando eventi di registro

public delegate void logDelegate(string str);
public class Operation
{
    public event logDelegate EventLogHandler;
    public Operation()
    {
        new FileLog(this);
        new ConsoleLog(this);
    }

    public void DoWork()
    {
        EventLogHandler.Invoke("somthing is working");
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.