Comprendre les événements et les gestionnaires d'événements en C #

330

Je comprends le but des événements, en particulier dans le contexte de la création d'interfaces utilisateur. Je pense que c'est le prototype pour créer un événement:

public void EventName(object sender, EventArgs e);

Que font les gestionnaires d'événements, pourquoi sont-ils nécessaires et comment en créer un?

Levi Campbell
la source
10
Comme indiqué par @Andy, l'extrait de code décrit ici la méthode enregistrée pour l'événement, pas l'événement lui-même.
dthrasher le
Copie

Réponses:

661

Pour comprendre les gestionnaires d'événements, vous devez comprendre les délégués . En C # , vous pouvez considérer un délégué comme un pointeur (ou une référence) vers une méthode. Ceci est utile car le pointeur peut être transmis en tant que valeur.

Le concept central d'un délégué est sa signature, ou sa forme. C'est (1) le type de retour et (2) les arguments d'entrée. Par exemple, si nous créons un délégué void MyDelegate(object sender, EventArgs e), il ne peut pointer que vers des méthodes qui retournent voidet prennent un objectet EventArgs. Un peu comme un trou carré et une cheville carrée. Nous disons donc que ces méthodes ont la même signature, ou forme, que le délégué.

Donc, sachant comment créer une référence à une méthode, réfléchissons au but des événements: nous voulons faire exécuter du code quand quelque chose se passe ailleurs dans le système - ou "gérer l'événement". Pour ce faire, nous créons des méthodes spécifiques pour le code que nous voulons exécuter. La colle entre l'événement et les méthodes à exécuter sont les délégués. L'événement doit stocker en interne une "liste" de pointeurs vers les méthodes à appeler lorsque l'événement est déclenché. * Bien sûr, pour pouvoir appeler une méthode, nous devons savoir quels arguments lui passer! Nous utilisons le délégué comme «contrat» entre l'événement et toutes les méthodes spécifiques qui seront appelées.

Ainsi, la valeur par défaut EventHandler(et beaucoup comme elle) représente une forme spécifique de méthode (encore une fois, void / object-EventArgs). Lorsque vous déclarez un événement, vous dites quelle forme de méthode (EventHandler) cet événement invoquera, en spécifiant un délégué:

//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");

(* Ceci est la clé des événements dans .NET et décolle la "magie" - un événement est vraiment, sous les couvertures, juste une liste de méthodes de la même "forme". La liste est stockée où l'événement vit. Quand l'événement est "déclenché", c'est vraiment juste "parcourez cette liste de méthodes et appelez chacune, en utilisant ces valeurs comme paramètres". L'affectation d'un gestionnaire d'événements est juste une manière plus jolie et plus simple d'ajouter votre méthode à cette liste de méthodes être appelé).

Rex M
la source
24
Et maintenant, quelqu'un peut-il expliquer pourquoi l'événement s'appelle EventHandler ?? De toutes les conventions de dénomination déroutantes, c'est la pire ...
Joel in Gö
37
@Joel in Go, l'événement ne s'appelle pas EventHandler - EventHandler est le contrat que l'événement doit avoir avec quiconque communique avec lui. C'est comme "chaîne MyString" - la chaîne déclare le type. event MyEventHandler TheEvent déclare que toute personne qui interagit avec cet événement doit se conformer au contrat MyEventHandler. La convention du gestionnaire est due au fait que le contrat décrit principalement comment gérer l'événement.
Rex M
18
Comment l'événement est-il déclenché?
alchimique
17
@Rex M: merci pour la première explication cohérente de "MyEventHandler" que j'ai jamais vue :)
Joel in Gö
10
Merci pour la phase: "La colle entre l'événement et les méthodes à exécuter sont les délégués.", C'est vraiment génial.
zionpi
103

C # connaît deux termes, delegateet event. Commençons par le premier.

Déléguer

A delegateest une référence à une méthode. Tout comme vous pouvez créer une référence à une instance:

MyClass instance = myFactory.GetInstance();

Vous pouvez utiliser un délégué pour créer une référence à une méthode:

Action myMethod = myFactory.GetInstance;

Maintenant que vous avez cette référence à une méthode, vous pouvez appeler la méthode via la référence:

MyClass instance = myMethod();

Mais pourquoi le feriez-vous? Vous pouvez également simplement appeler myFactory.GetInstance()directement. Dans ce cas, vous le pouvez. Cependant, il existe de nombreux cas où vous ne souhaitez pas que le reste de l'application connaisse myFactoryou appelle myFactory.GetInstance()directement.

Un élément évident est si vous voulez pouvoir remplacer myFactory.GetInstance()à myOfflineFakeFactory.GetInstance()partir d'un endroit central (aka modèle de méthode d'usine ).

Modèle de méthode d'usine

Donc, si vous avez une TheOtherClassclasse et qu'elle doit utiliser le myFactory.GetInstance(), voici à quoi ressemblera le code sans délégués (vous devrez faire TheOtherClassconnaître le type de votre myFactory):

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


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

}

Si vous utilisez des délégués, vous n'avez pas à exposer le type de mon usine:

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


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

}

Ainsi, vous pouvez donner un délégué à une autre classe à utiliser, sans leur exposer votre type. La seule chose que vous exposez est la signature de votre méthode (combien de paramètres vous avez, etc.).

"Signature de ma méthode", où ai-je entendu ça avant? O oui, les interfaces !!! les interfaces décrivent la signature d'une classe entière. Considérez les délégués comme décrivant la signature d'une seule méthode!

Une autre grande différence entre une interface et un délégué est que lorsque vous écrivez votre classe, vous n'avez pas à dire à C # "cette méthode implémente ce type de délégué". Avec les interfaces, vous devez dire "cette classe implémente ce type d'interface".

En outre, une référence de délégué peut (avec certaines restrictions, voir ci-dessous) référencer plusieurs méthodes (appelées MulticastDelegate). Cela signifie que lorsque vous appelez le délégué, plusieurs méthodes explicitement attachées seront exécutées. Une référence d'objet ne peut toujours faire référence qu'à un seul objet.

Les restrictions pour a MulticastDelegatesont que la signature (méthode / délégué) ne doit pas avoir de valeur de retour ( void) et les mots clés outet refn'est pas utilisée dans la signature. De toute évidence, vous ne pouvez pas appeler deux méthodes qui renvoient un nombre et s'attendre à ce qu'elles renvoient le même numéro. Une fois la signature conforme, le délégué est automatiquement a MulticastDelegate.

un événement

Les événements ne sont que des propriétés (comme le get; set; les propriétés des champs d'instance) qui exposent l'abonnement au délégué d'autres objets. Cependant, ces propriétés ne prennent pas en charge get; set ;. Au lieu de cela, ils prennent en charge l'ajout; retirer;

Vous pouvez donc avoir:

    Action myField;

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

Utilisation dans l'interface utilisateur (WinForms, WPF, UWP ainsi de suite)

Donc, maintenant nous savons qu'un délégué est une référence à une méthode et que nous pouvons avoir un événement pour faire savoir au monde qu'il peut nous donner ses méthodes à référencer à partir de notre délégué, et nous sommes un bouton d'interface utilisateur, alors: nous peut demander à toute personne intéressée de savoir si j'ai été cliquée, d'enregistrer sa méthode avec nous (via l'événement que nous avons exposé). Nous pouvons utiliser toutes les méthodes qui nous ont été données et les référencer par notre délégué. Et puis, nous attendrons et attendrons .... jusqu'à ce qu'un utilisateur vienne et clique sur ce bouton, nous aurons alors suffisamment de raisons d'invoquer le délégué. Et parce que le délégué fait référence à toutes ces méthodes qui nous sont données, toutes ces méthodes seront invoquées. Nous ne savons pas ce que font ces méthodes, ni nous savons quelle classe implémente ces méthodes. Tout ce qui nous importe, c'est que quelqu'un était intéressé à ce qu'on clique sur nous,

Java

Les langages comme Java n'ont pas de délégués. Ils utilisent plutôt des interfaces. La façon dont ils le font est de demander à toute personne intéressée par `` qu'on clique '', d'implémenter une certaine interface (avec une certaine méthode que nous pouvons appeler), puis de nous donner l'instance entière qui implémente l'interface. Nous gardons une liste de tous les objets implémentant cette interface et pouvons appeler leur «certaine méthode que nous pouvons appeler» chaque fois que nous cliquons.

tofi9
la source
acclamations pour l'explication, mais comment un événement diffère-t-il d'une instance d'un délégué qui prend des abonnés? ils ressemblent tous les deux exactement à la même chose?
BKSpurgeon
@BKSpurgeon c'est parce que ce sont des "délégués qui prennent des abonnés" - ce eventn'est que du sucre de syntaxe, rien de plus.
Mathieu Guindon
"Les restrictions pour un MulticastDelegate sont que la signature (méthode / délégué) ne doit pas avoir de valeur de retour (void)", je ne pense pas que ce soit correct. S'ils ont des valeurs de retour, il retournera la dernière.
Hozikimaru
"Ainsi, vous pouvez donner un délégué à une autre classe à utiliser, sans leur exposer votre type. La seule chose que vous exposez est la signature de votre méthode ..." - c'est pour moi le point critique. Je vous remercie!
Ryan
40

Il s'agit en fait de la déclaration d'un gestionnaire d'événements - une méthode qui sera appelée lorsqu'un événement est déclenché. Pour créer un événement, vous écririez quelque chose comme ceci:

public class Foo
{
    public event EventHandler MyEvent;
}

Et puis vous pouvez vous abonner à l'événement comme ceci:

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

Avec OnMyEvent () défini comme ceci:

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

Chaque fois que le Footir se déclenche MyEvent, votre OnMyEventgestionnaire sera appelé.

Vous n'avez pas toujours besoin d'utiliser une instance de EventArgscomme deuxième paramètre. Si vous souhaitez inclure des informations supplémentaires, vous pouvez utiliser une classe dérivée de EventArgs( EventArgsest la base par convention). Par exemple, si vous regardez certains des événements définis Controldans WinForms ou FrameworkElementdans WPF, vous pouvez voir des exemples d'événements qui transmettent des informations supplémentaires aux gestionnaires d'événements.

Andy
la source
14
Merci d'avoir répondu à la question et de ne pas aborder les délégués et les événements.
divide_byzero
3
Je déconseille d'utiliser le OnXXXmodèle de dénomination pour votre gestionnaire d'événements. (Stupidement, OnXXX est censé signifier «gérer XXX» dans MFC et «augmenter XXX» dans .net, et maintenant sa signification n'est pas claire et prête à confusion - voir ce post pour plus de détails ). Les noms préférés seraient RaiseXXXpour déclencher des événements et / HandleXXXou Sender_XXXpour les gestionnaires d'événements.
Jason Williams
1
Pouvez-vous montrer un exemple de travail avec une simple application WinForms?
MC9000
40

Voici un exemple de code qui peut vous aider:

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();
    }
  }
}
Gary Willoughby
la source
4
L'invocation des délégués en C # 6 peut être simplifiée pour:OnMaximum?.Invoke(this,new MyEventArgs("you've entered..."));
Tim Schmelter
23

Juste pour ajouter aux bonnes réponses existantes ici - en s'appuyant sur le code de la réponse acceptée, qui utilise un delegate void MyEventHandler(string foo)...

Parce que le compilateur connaît le type délégué de l' événement SomethingHappened , ceci:

myObj.SomethingHappened += HandleSomethingHappened;

Est totalement équivalent à:

myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

Et les gestionnaires peuvent aussi être non enregistrée avec -=comme ceci:

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

Par souci d'exhaustivité, le déclenchement de l'événement peut se faire comme ceci, uniquement dans la classe qui possède l'événement:

//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 copie locale du thread du gestionnaire est nécessaire pour s'assurer que l'invocation est thread-safe - sinon un thread pourrait aller désenregistrer le dernier gestionnaire de l'événement immédiatement après que nous ayons vérifié s'il l'était null, et nous nous amuserions NullReferenceExceptionlà-bas. .


C # 6 a introduit une belle main courte pour ce modèle. Il utilise l'opérateur de propagation nul.

SomethingHappened?.Invoke("Hi there!");
Mathieu Guindon
la source
13

Ma compréhension des événements est;

Déléguer:

Une variable pour contenir une référence à la ou aux méthodes à exécuter. Cela permet de passer des méthodes comme une variable.

Étapes pour créer et appeler l'événement:

  1. L'événement est une instance d'un délégué

  2. Puisqu'un événement est une instance d'un délégué, nous devons d'abord définir le délégué.

  3. Attribuer la ou les méthodes à exécuter lors du déclenchement de l'événement ( appel du délégué )

  4. Déclencher l'événement ( appeler le délégué )

Exemple:

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

    }   

}
KE50
la source
3

éditeur: où les événements se produisent. L'éditeur doit spécifier quel délégué la classe utilise et générer les arguments nécessaires, transmettre ces arguments et lui-même au délégué.

abonné: où la réponse se produit. L'abonné doit spécifier des méthodes pour répondre aux événements. Ces méthodes doivent prendre le même type d'arguments que le délégué. L'abonné ajoute ensuite cette méthode au délégué de l'éditeur.

Par conséquent, lorsque l'événement se produit dans l'éditeur, le délégué recevra certains arguments d'événement (données, etc.), mais l'éditeur n'a aucune idée de ce qui se passera avec toutes ces données. Les abonnés peuvent créer des méthodes dans leur propre classe pour répondre aux événements de la classe de l'éditeur, afin que les abonnés puissent répondre aux événements de l'éditeur.

rileyss
la source
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);
Bilgi Sayar
la source
0

Je suis d'accord avec KE50 sauf que je vois le mot-clé 'event' comme un alias pour 'ActionCollection' car l'événement contient une collection d'actions à effectuer (c'est-à-dire le délégué).

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

}   

}
user3902302
la source
0

Excellentes réponses techniques dans le post! Je n'ai rien à ajouter techniquement à cela.

L'une des principales raisons pour lesquelles les nouvelles fonctionnalités apparaissent dans les langues et les logiciels en général est le marketing ou la politique d'entreprise! :-) Cela ne doit pas être sous-estimé!

Je pense que cela s'applique également à certains délégués et événements! je les trouve utiles et ajoute de la valeur au langage C #, mais d'un autre côté le langage Java a décidé de ne pas les utiliser! ils ont décidé que quoi que vous résolviez avec les délégués, vous pouvez déjà le résoudre avec les fonctionnalités existantes du langage, c'est-à-dire les interfaces, par exemple

Maintenant, vers 2001, Microsoft a publié le framework .NET et le langage C # en tant que solution concurrente de Java, donc c'était bien d'avoir de NOUVELLES FONCTIONNALITÉS que Java n'a pas.

Siraf
la source
0

J'ai récemment fait un exemple d'utilisation des événements en c # et je l'ai posté sur mon blog. J'ai essayé de le rendre aussi clair que possible, avec un exemple très simple. Au cas où cela pourrait aider quelqu'un, le voici: http://www.konsfik.com/using-events-in-csharp/

Il comprend la description et le code source (avec beaucoup de commentaires), et il se concentre principalement sur une utilisation correcte (de type modèle) des événements et des gestionnaires d'événements.

Quelques points clés:

  • Les événements sont comme des "sous-types de délégués", mais plus contraints (dans le bon sens). En fait, la déclaration d'un événement inclut toujours un délégué (les EventHandlers sont un type de délégué).

  • Les gestionnaires d'événements sont des types spécifiques de délégués (vous pouvez les considérer comme un modèle), ce qui oblige l'utilisateur à créer des événements qui ont une "signature" spécifique. La signature est au format: (expéditeur d'objet, arguments d'événement EventArgs).

  • Vous pouvez créer votre propre sous-classe d'EventArgs, afin d'inclure tout type d'informations que l'événement doit transmettre. Il n'est pas nécessaire d'utiliser EventHandlers lors de l'utilisation d'événements. Vous pouvez les ignorer complètement et utiliser votre propre type de délégué à leur place.

  • Une différence clé entre l'utilisation d'événements et de délégués est que les événements ne peuvent être invoqués qu'à partir de la classe dans laquelle ils ont été déclarés, même s'ils peuvent être déclarés publics. Il s'agit d'une distinction très importante, car elle permet à vos événements d'être exposés afin qu'ils soient "connectés" à des méthodes externes, tout en étant protégés contre les "abus externes".

konsfik
la source
0

Une autre chose à savoir , dans certains cas, vous devez utiliser les délégués / événements lorsque vous avez besoin d'un faible niveau de couplage !

Si vous souhaitez utiliser un composant à plusieurs endroits dans l'application , vous devez créer un composant avec un faible niveau de couplage et la LOGIQUE spécifique non concernée doit être déléguée EN DEHORS de votre composant! Cela garantit que vous disposez d'un système découplé et d'un code plus propre.

En principe SOLIDE c'est le " D ", ( principe d'inversion de dépendance D ).

Aussi connu comme " IoC ", inversion de contrôle .

Vous pouvez faire " IoC " avec des événements, des délégués et DI (injection de dépendance).

Il est facile d'accéder à une méthode dans une classe enfant. Mais plus difficile d'accéder à une méthode dans une classe parent à partir d'un enfant. Vous devez transmettre la référence parent à l'enfant! (ou utilisez DI avec interface)

Les délégués / événements nous permettent de communiquer de l'enfant au parent sans référence!

entrez la description de l'image ici

Dans ce diagramme ci-dessus, je n'utilise pas Délégué / Événement et le composant parent B doit avoir une référence du composant parent A pour exécuter la logique métier non concernée dans la méthode A. (niveau de couplage élevé)

Avec cette approche, je devrais mettre toutes les références de tous les composants qui utilisent le composant B! :(

entrez la description de l'image ici

Dans ce diagramme ci-dessus, j'utilise Délégué / Événement et le composant B n'a pas à connaître A. (bas niveau de couplage)

Et vous pouvez utiliser votre composant B n'importe où dans votre application !

A. Morel
la source