Comprensione di eventi e gestori di eventi in C #


330

Comprendo lo scopo degli eventi, soprattutto nel contesto della creazione di interfacce utente. Penso che questo sia il prototipo per la creazione di un evento:

public void EventName(object sender, EventArgs e);

Cosa fanno i gestori di eventi, perché sono necessari e come posso crearne uno?


10
Come notato da @Andy, lo snippet di codice qui descrive il metodo registrato all'evento, non l'evento stesso.
Dthrasher,


Risposte:


661

Per comprendere i gestori di eventi, è necessario comprendere i delegati . In C # , puoi pensare a un delegato come un puntatore (o un riferimento) a un metodo. Questo è utile perché il puntatore può essere passato come valore.

Il concetto centrale di un delegato è la sua firma o forma. Questo è (1) il tipo restituito e (2) gli argomenti di input. Ad esempio, se creiamo un delegato void MyDelegate(object sender, EventArgs e), può solo puntare a metodi che restituiscono voide prendono un objecte EventArgs. Un po 'come un buco quadrato e un piolo quadrato. Quindi diciamo che questi metodi hanno la stessa firma o forma del delegato.

Quindi, sapendo come creare un riferimento a un metodo, pensiamo allo scopo degli eventi: vogliamo far eseguire del codice quando qualcosa accade altrove nel sistema - o "gestire l'evento". Per fare ciò, creiamo metodi specifici per il codice che vogliamo eseguire. La colla tra l'evento e i metodi da eseguire sono i delegati. L'evento deve memorizzare internamente un "elenco" di puntatori ai metodi da chiamare quando l'evento viene generato. * Naturalmente, per poter chiamare un metodo, dobbiamo sapere quali argomenti passare ad esso! Usiamo il delegato come "contratto" tra l'evento e tutti i metodi specifici che verranno chiamati.

Quindi il valore predefinito EventHandler(e molti simili) rappresenta una forma specifica del metodo (di nuovo, void / object-EventArgs). Quando dichiari un evento, stai dicendo quale forma di metodo (EventHandler) quell'evento invocherà, specificando un delegato:

//This delegate can be used to point to methods
//which return void and take a string.
public delegate void MyEventHandler(string foo);

//This event can cause any method which conforms
//to MyEventHandler to be called.
public event MyEventHandler SomethingHappened;

//Here is some code I want to be executed
//when SomethingHappened fires.
void HandleSomethingHappened(string foo)
{
    //Do some stuff
}

//I am creating a delegate (pointer) to HandleSomethingHappened
//and adding it to SomethingHappened's list of "Event Handlers".
myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

//To raise the event within a method.
SomethingHappened("bar");

(* Questa è la chiave degli eventi in .NET e toglie la "magia" - un evento è in realtà, sotto le copertine, solo un elenco di metodi della stessa "forma". L'elenco è memorizzato dove vive l'evento. Quando l'evento è "sollevato", in realtà è solo "passare attraverso questo elenco di metodi e chiamare ciascuno, utilizzando questi valori come parametri". L'assegnazione di un gestore di eventi è solo un modo più carino e più semplice di aggiungere il metodo a questo elenco di metodi da chiamare).


24
E ora qualcuno può spiegare perché l'evento si chiama EventHandler ?? Di tutte le convenzioni di denominazione confuse, questa è la peggiore ...
Joel in Gö

37
@Joel in Go l'evento non si chiama EventHandler - EventHandler è il contratto che l'evento deve avere con chiunque comunichi con esso. È come "stringa MyString" - la stringa sta dichiarando il tipo. event MyEventHandler TheEvent dichiara che chiunque interagisca con questo evento deve conformarsi al contratto MyEventHandler. La convenzione del gestore è perché il contratto descrive principalmente come gestire l'evento.
Rex M,

18
Come viene generato l'evento?
alchemico

17
@Rex M: grazie per la prima spiegazione coerente di "MyEventHandler" che io abbia mai visto :)
Joel in Gö

10
Grazie per la fase: "La colla tra l'evento e i metodi da eseguire sono i delegati", è davvero fantastico.
zionpi,

103

C # conosce due termini delegatee event. Cominciamo con il primo.

Delegare

A delegateè un riferimento a un metodo. Proprio come puoi creare un riferimento a un'istanza:

MyClass instance = myFactory.GetInstance();

È possibile utilizzare un delegato per creare un riferimento a un metodo:

Action myMethod = myFactory.GetInstance;

Ora che hai questo riferimento a un metodo, puoi chiamare il metodo tramite il riferimento:

MyClass instance = myMethod();

Ma perché dovresti? Puoi anche semplicemente chiamare myFactory.GetInstance()direttamente. In questo caso puoi. Tuttavia, ci sono molti casi in cui pensare a dove non si desidera che il resto dell'applicazione sia a conoscenza myFactoryo che chiami myFactory.GetInstance()direttamente.

Un più ovvia è che se si vuole essere in grado di sostituire myFactory.GetInstance()in myOfflineFakeFactory.GetInstance()da un posto centrale (aka metodo factory modello ).

Modello del metodo di fabbrica

Quindi, se hai una TheOtherClassclasse e devi usarla myFactory.GetInstance(), ecco come apparirà il codice senza delegati (dovrai far TheOtherClasssapere il tipo del tuo myFactory):

TheOtherClass toc;
//...
toc.SetFactory(myFactory);


class TheOtherClass
{
   public void SetFactory(MyFactory factory)
   {
      // set here
   }

}

Se utilizzi delegati, non devi esporre il tipo di mia fabbrica:

TheOtherClass toc;
//...
Action factoryMethod = myFactory.GetInstance;
toc.SetFactoryMethod(factoryMethod);


class TheOtherClass
{
   public void SetFactoryMethod(Action factoryMethod)
   {
      // set here
   }

}

Pertanto, puoi assegnare un delegato a un'altra classe da utilizzare, senza esporre il tuo tipo a loro. L'unica cosa che stai esponendo è la firma del tuo metodo (quanti parametri hai e simili).

"Firma del mio metodo", dove l'ho già sentito? O sì, interfacce !!! le interfacce descrivono la firma di un'intera classe. Pensa ai delegati come a descrivere la firma di un solo metodo!

Un'altra grande differenza tra un'interfaccia e un delegato è che quando scrivi la tua classe, non devi dire a C # "questo metodo implementa quel tipo di delegato". Con le interfacce, devi dire "questa classe implementa quel tipo di interfaccia".

Inoltre, un riferimento delegato può (con alcune restrizioni, vedere di seguito) fare riferimento a più metodi (chiamati MulticastDelegate). Ciò significa che quando si chiama il delegato, verranno eseguiti più metodi esplicitamente collegati. Un riferimento a un oggetto può sempre fare riferimento solo a un oggetto.

Le restrizioni per a MulticastDelegatesono che la firma (metodo / delegato) non deve avere alcun valore restituito ( void) e le parole chiave oute refnon viene utilizzata nella firma. Ovviamente, non puoi chiamare due metodi che restituiscono un numero e aspettarti che restituiscano lo stesso numero. Una volta che la firma è conforme, il delegato è automaticamente a MulticastDelegate.

Evento

Gli eventi sono solo proprietà (come get; set; proprietà nei campi istanza) che espongono la sottoscrizione al delegato da altri oggetti. Queste proprietà, tuttavia, non supportano get; set ;. Al contrario, supportano add; rimuovere;

Quindi puoi avere:

    Action myField;

    public event Action MyProperty
    {
        add { myField += value; }
        remove { myField -= value; }
    }

Utilizzo nell'interfaccia utente (WinForms, WPF, UWP e così via)

Quindi, ora sappiamo che un delegato è un riferimento a un metodo e che possiamo avere un evento per far sapere al mondo che possono darci i loro metodi per essere referenziati dal nostro delegato, e noi siamo un pulsante dell'interfaccia utente, quindi: noi può chiedere a chiunque sia interessato a sapere se sono stato cliccato, di registrare il nostro metodo con noi (tramite l'evento che abbiamo esposto). Possiamo usare tutti quei metodi che ci sono stati dati e fare riferimento a loro dal nostro delegato. E poi, aspetteremo e aspetteremo ... fino a quando un utente arriva e fa clic su quel pulsante, quindi avremo abbastanza motivi per invocare il delegato. E poiché il delegato fa riferimento a tutti quei metodi che ci vengono forniti, tutti questi metodi verranno invocati. Non sappiamo cosa fanno questi metodi, né sappiamo quale classe implementa quei metodi. Ci interessa solo che qualcuno fosse interessato a fare clic su di noi,

Giava

Lingue come Java non hanno delegati. Usano invece interfacce. Il modo in cui lo fanno è chiedere a chiunque sia interessato a "essere cliccato", di implementare una certa interfaccia (con un certo metodo che possiamo chiamare), quindi fornirci l'intera istanza che implementa l'interfaccia. Manteniamo un elenco di tutti gli oggetti che implementano questa interfaccia e possiamo chiamare il loro "certo metodo che possiamo chiamare" ogni volta che facciamo clic.


applausi per la spiegazione, ma in che modo un evento si differenzia da un'istanza di un delegato che assume abbonati? sembrano entrambi esattamente la stessa cosa?
BKSpurgeon,

@BKSpurgeon è perché sono "delegati che assumono abbonati" - eventè semplice zucchero di sintassi, niente di più.
Mathieu Guindon,

"Le restrizioni per un MulticastDelegate sono che la firma (metodo / delegato) non dovrebbe avere alcun valore di ritorno (vuoto)", non credo che sia corretto. Se hanno valori di ritorno, restituirà l'ultimo.
Hozikimaru,

"Quindi, puoi dare un delegato ad un'altra classe da usare, senza esporre loro il tuo tipo. L'unica cosa che stai esponendo è la firma del tuo metodo ..." - questo per me è il punto critico. Grazie!
Ryan

40

Questa è in realtà la dichiarazione per un gestore di eventi, un metodo che verrà chiamato quando viene generato un evento. Per creare un evento, dovresti scrivere qualcosa del genere:

public class Foo
{
    public event EventHandler MyEvent;
}

E poi puoi iscriverti all'evento in questo modo:

Foo foo = new Foo();
foo.MyEvent += new EventHandler(this.OnMyEvent);

Con OnMyEvent () definito in questo modo:

private void OnMyEvent(object sender, EventArgs e)
{
    MessageBox.Show("MyEvent fired!");
}

Ogni volta che Foosi spegne MyEvent, OnMyEventverrà chiamato il tuo gestore.

Non è sempre necessario utilizzare un'istanza di EventArgscome secondo parametro. Se si desidera includere informazioni aggiuntive, è possibile utilizzare una classe derivata da EventArgs( EventArgsè la base per convenzione). Ad esempio, se si osservano alcuni degli eventi definiti Controlin WinForms o FrameworkElementin WPF, è possibile vedere esempi di eventi che trasmettono informazioni aggiuntive ai gestori di eventi.


14
Grazie per aver risposto alla domanda e non aver partecipato a delegati ed eventi.
divide_byzero il

3
Consiglio di non utilizzare il OnXXXmodello di denominazione per il gestore dell'evento. (Stupidamente, OnXXX significa "gestire XXX" in MFC e "aumentare XXX" in .net, e quindi ora il suo significato non è chiaro e confuso - vedi questo post per i dettagli ). I nomi preferiti sarebbero quelli RaiseXXXdi generare eventi HandleXXXo Sender_XXXdi gestori di eventi.
Jason Williams,

1
Puoi mostrare un esempio funzionante con una semplice applicazione WinForms?
MC9000,

40

Ecco un esempio di codice che può aiutare:

using System;
using System.Collections.Generic;
using System.Text;

namespace Event_Example
{
  // First we have to define a delegate that acts as a signature for the
  // function that is ultimately called when the event is triggered.
  // You will notice that the second parameter is of MyEventArgs type.
  // This object will contain information about the triggered event.

  public delegate void MyEventHandler(object source, MyEventArgs e);

  // This is a class which describes the event to the class that receives it.
  // An EventArgs class must always derive from System.EventArgs.

  public class MyEventArgs : EventArgs
  {
    private string EventInfo;

    public MyEventArgs(string Text) {
      EventInfo = Text;
    }

    public string GetInfo() {
      return EventInfo;
    }
  }

  // This next class is the one which contains an event and triggers it
  // once an action is performed. For example, lets trigger this event
  // once a variable is incremented over a particular value. Notice the
  // event uses the MyEventHandler delegate to create a signature
  // for the called function.

  public class MyClass
  {
    public event MyEventHandler OnMaximum;

    private int i;
    private int Maximum = 10;

    public int MyValue
    {
      get { return i; }
      set
      {
        if(value <= Maximum) {
          i = value;
        }
        else 
        {
          // To make sure we only trigger the event if a handler is present
          // we check the event to make sure it's not null.
          if(OnMaximum != null) {
            OnMaximum(this, new MyEventArgs("You've entered " +
              value.ToString() +
              ", but the maximum is " +
              Maximum.ToString()));
          }
        }
      }
    }
  }

  class Program
  {
    // This is the actual method that will be assigned to the event handler
    // within the above class. This is where we perform an action once the
    // event has been triggered.

    static void MaximumReached(object source, MyEventArgs e) {
      Console.WriteLine(e.GetInfo());
    }

    static void Main(string[] args) {
      // Now lets test the event contained in the above class.
      MyClass MyObject = new MyClass();
      MyObject.OnMaximum += new MyEventHandler(MaximumReached);
      for(int x = 0; x <= 15; x++) {
        MyObject.MyValue = x;
      }
      Console.ReadLine();
    }
  }
}

4
L'invocazione dei delegati in C # 6 può essere semplificata in:OnMaximum?.Invoke(this,new MyEventArgs("you've entered..."));
Tim Schmelter,

23

Solo per aggiungere alle grandi risposte esistenti qui - basandosi sul codice in quello accettato, che utilizza un delegate void MyEventHandler(string foo)...

Poiché il compilatore conosce il tipo delegato dell'evento SomethingHappened , questo:

myObj.SomethingHappened += HandleSomethingHappened;

È totalmente equivalente a:

myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

E i gestori possono anche essere non registrati con -=questo modo:

// -= removes the handler from the event's list of "listeners":
myObj.SomethingHappened -= HandleSomethingHappened;

Per completezza, l'innalzamento dell'evento può essere fatto in questo modo, solo nella classe che possiede l'evento:

//Firing the event is done by simply providing the arguments to the event:
var handler = SomethingHappened; // thread-local copy of the event
if (handler != null) // the event is null if there are no listeners!
{
    handler("Hi there!");
}

La copia locale del gestore del thread è necessaria per assicurarsi che l'invocazione sia thread-safe, altrimenti un thread potrebbe andare e annullare la registrazione dell'ultimo gestore dell'evento immediatamente dopo aver verificato se lo fosse null, e ci divertiremmo NullReferenceExceptionlì .


C # 6 ha introdotto una bella scorciatoia per questo modello. Utilizza l'operatore di propagazione null.

SomethingHappened?.Invoke("Hi there!");

13

La mia comprensione degli eventi è;

Delegare:

Una variabile per contenere il riferimento al metodo / ai metodi da eseguire. Ciò rende possibile il passaggio di metodi come una variabile.

Passaggi per creare e chiamare l'evento:

  1. L'evento è un'istanza di un delegato

  2. Poiché un evento è un'istanza di un delegato, dobbiamo prima definire il delegato.

  3. Assegnare il metodo / i metodi da eseguire quando l'evento viene generato ( Chiamare il delegato )

  4. Attiva l'evento ( chiama il delegato )

Esempio:

using System;

namespace test{
    class MyTestApp{
        //The Event Handler declaration
        public delegate void EventHandler();

        //The Event declaration
        public event EventHandler MyHandler;

        //The method to call
        public void Hello(){
            Console.WriteLine("Hello World of events!");
        }

        public static void Main(){
            MyTestApp TestApp = new MyTestApp();

            //Assign the method to be called when the event is fired
            TestApp.MyHandler = new EventHandler(TestApp.Hello);

            //Firing the event
            if (TestApp.MyHandler != null){
                TestApp.MyHandler();
            }
        }

    }   

}

3

editore: dove accadono gli eventi. L'editore deve specificare quale delegato sta utilizzando la classe e generare gli argomenti necessari, passare tali argomenti e se stesso al delegato.

abbonato: dove avviene la risposta. Il sottoscrittore deve specificare i metodi per rispondere agli eventi. Questi metodi dovrebbero assumere lo stesso tipo di argomenti del delegato. Il sottoscrittore quindi aggiunge questo metodo al delegato del publisher.

Pertanto, quando l'evento si verifica nell'editore, il delegato riceverà alcuni argomenti dell'evento (dati, ecc.), Ma l'editore non ha idea di cosa accadrà con tutti questi dati. Gli abbonati possono creare metodi nella propria classe per rispondere agli eventi nella classe dell'editore, in modo che gli abbonati possano rispondere agli eventi dell'editore.


2
//This delegate can be used to point to methods
//which return void and take a string.
public delegate void MyDelegate(string foo);

//This event can cause any method which conforms
//to MyEventHandler to be called.
public event MyDelegate MyEvent;

//Here is some code I want to be executed
//when SomethingHappened fires.
void MyEventHandler(string foo)
{
    //Do some stuff
}

//I am creating a delegate (pointer) to HandleSomethingHappened
//and adding it to SomethingHappened's list of "Event Handlers".
myObj.MyEvent += new MyDelegate (MyEventHandler);

0

Sono d'accordo con KE50, tranne per il fatto che vedo la parola chiave "evento" come un alias per "ActionCollection" poiché l'evento contiene una raccolta di azioni da eseguire (ad es. Il delegato).

using System;

namespace test{

class MyTestApp{
    //The Event Handler declaration
    public delegate void EventAction();

    //The Event Action Collection 
    //Equivalent to 
    //  public List<EventAction> EventActions=new List<EventAction>();
    //        
    public event EventAction EventActions;

    //An Action
    public void Hello(){
        Console.WriteLine("Hello World of events!");
    }
    //Another Action
    public void Goodbye(){
        Console.WriteLine("Goodbye Cruel World of events!");
    }

    public static void Main(){
        MyTestApp TestApp = new MyTestApp();

        //Add actions to the collection
        TestApp.EventActions += TestApp.Hello;
        TestApp.EventActions += TestApp.Goodbye;

        //Invoke all event actions
        if (TestApp.EventActions!= null){
            //this peculiar syntax hides the invoke 
            TestApp.EventActions();
            //using the 'ActionCollection' idea:
            // foreach(EventAction action in TestApp.EventActions)
            //     action.Invoke();
        }
    }

}   

}

0

Grandi risposte tecniche nel post! Tecnicamente non ho nulla da aggiungere a questo.

Uno dei motivi principali per cui nuove funzionalità appaiono in lingue e software in generale è il marketing o la politica aziendale! :-) Questo non deve essere sottovalutato!

Penso che ciò si applichi in certa misura anche ai delegati e agli eventi! li trovo utili e aggiungo valore al linguaggio C #, ma d'altra parte il linguaggio Java ha deciso di non usarli! hanno deciso che qualunque cosa tu stia risolvendo con i delegati, puoi già risolvere con le funzionalità esistenti del linguaggio, ad es. interfacce es

Ora intorno al 2001 Microsoft ha rilasciato .NET framework e il linguaggio C # come soluzione concorrente di Java, quindi è stato bello avere NUOVE CARATTERISTICHE che Java non ha.


0

Di recente ho fatto un esempio di come utilizzare gli eventi in c # e l'ho pubblicato sul mio blog. Ho cercato di renderlo il più chiaro possibile, con un esempio molto semplice. Nel caso in cui possa aiutare chiunque, eccolo qui: http://www.konsfik.com/using-events-in-csharp/

Include la descrizione e il codice sorgente (con molti commenti) e si concentra principalmente su un uso corretto (simile a un modello) di eventi e gestori di eventi.

Alcuni punti chiave sono:

  • Gli eventi sono come "sottotipi di delegati", solo più vincolati (in senso buono). In effetti la dichiarazione di un evento include sempre un delegato (EventHandlers è un tipo di delegato).

  • I gestori di eventi sono tipi specifici di delegati (potresti considerarli come un modello), che costringono l'utente a creare eventi con una "firma" specifica. La firma è nel formato: (mittente oggetto, eventi EventArgs).

  • È possibile creare la propria sottoclasse di EventArgs, al fine di includere qualsiasi tipo di informazione che l'evento deve trasmettere. Non è necessario utilizzare EventHandlers quando si utilizzano eventi. Puoi saltarli completamente e usare il tuo tipo di delegato al loro posto.

  • Una differenza fondamentale tra l'utilizzo di eventi e delegati è che gli eventi possono essere invocati solo all'interno della classe in cui sono stati dichiarati, anche se possono essere dichiarati come pubblici. Questa è una distinzione molto importante, perché consente ai tuoi eventi di essere esposti in modo che siano "collegati" a metodi esterni, mentre allo stesso tempo sono protetti da "abusi esterni".


0

Un'altra cosa da sapere , in alcuni casi, è necessario utilizzare i delegati / eventi quando è necessario un basso livello di accoppiamento !

Se si desidera utilizzare un componente in diversi punti dell'applicazione , è necessario creare un componente con un basso livello di accoppiamento e la LOGICA specifica non interessata deve essere delegata ESTERNA del proprio componente! Questo assicura che tu abbia un sistema disaccoppiato e un codice più pulito.

In SOLID principio questa è la " D ", ( D ependency principio inversione).

Conosciuto anche come " IoC ", inversione di controllo .

Puoi creare " IoC " con Eventi, Delegati e DI (Dependency Injection).

È facile accedere a un metodo in una classe figlio. Ma è più difficile accedere a un metodo in una classe genitore da figlio. Devi passare il riferimento del genitore al figlio! (o usa DI con l'interfaccia)

Delegati / Eventi ci consente di comunicare dal bambino al genitore senza riferimento!

inserisci qui la descrizione dell'immagine

In questo diagramma sopra, non uso Delegato / Evento e il componente principale B deve avere un riferimento del componente principale A per eseguire la logica aziendale non interessata nel metodo A. (alto livello di accoppiamento)

Con questo approccio, dovrei mettere tutti i riferimenti di tutti i componenti che usano il componente B! :(

inserisci qui la descrizione dell'immagine

In questo diagramma sopra, uso Delegato / Evento e il componente B non deve conoscere A. (basso livello di accoppiamento)

E puoi usare il tuo componente B ovunque nella tua applicazione !

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.