web-dev-qa-db-fra.com

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

Je comprends le but des événements, en particulier dans le contexte de la création d'interfaces utilisateur. Je pense que ceci 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 puis-je en créer un?

286
Levi Campbell

Pour comprendre les gestionnaires d'événements, vous devez comprendre délégués . Dans 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 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 sur les méthodes qui renvoient void et prennent un object et 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 la même forme que le délégué.

Alors, 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 lorsque 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 sur 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 être transmis! 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 d’autres similaires) représente un forme spécifique de la méthode (encore une fois, void/object-EventArgs). Lorsque vous déclarez un événement, vous dites quelle forme de méthode (EventHandler) que cet événement va appeler, 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");

(* C’est la clé des événements dans .NET et enlève 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é", il s'agit simplement de "parcourir cette liste de méthodes et d'appeler chacune d'elles en utilisant ces valeurs comme paramètres". L'affectation d'un gestionnaire d'événement est un moyen plus joli et plus simple d'ajouter votre méthode à cette liste de méthodes. être appelé).

577
Rex M

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

Déléguer

Une delegate est 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 vous Vous pouvez aussi appeler myFactory.GetInstance() directement. Dans ce cas, vous pouvez. Cependant, il existe de nombreux cas où vous ne souhaitez pas que le reste de l'application connaisse myFactory ou appelle directement myFactory.GetInstance()

Une solution évidente est de pouvoir remplacer myFactory.GetInstance() par myOfflineFakeFactory.GetInstance() depuis un emplacement central (/ méthode de fabrique modèle ).

Modèle de méthode d'usine

Donc, si vous avez une classe TheOtherClass et qu'elle doit utiliser la fonction myFactory.GetInstance(), le code ressemblera à ceci sans délégués (vous devrez informer TheOtherClass du 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 exposer votre type à celles-ci. La seule chose que vous exposez est la signature de votre méthode (combien de paramètres vous avez et tel). 

"Signature de ma méthode", où ai-je entendu cela auparavant? O oui, interfaces !!! les interfaces décrivent la signature de toute une classe. Pensez aux 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 un MulticastDelegate sont que la signature (méthode/délégué) ne doit avoir aucune valeur de retour (void) et que les mots clés out et ref ne sont pas utilisés dans la signature. De toute évidence, vous ne pouvez pas appeler deux méthodes renvoyant un nombre et vous attendre à ce qu’elles renvoient le même nombre. Une fois que la signature est conforme, le délégué est automatiquement un MulticastDelegate.

Un événement

Les événements ne sont que des propriétés (telles que les propriétés get; set; propriétés des champs d'instance) qui exposent la souscription au délégué à partir d'autres objets. Cependant, ces propriétés ne supportent pas get; set ;. Au lieu de cela, ils soutiennent ajouter; retirer;

Pour que vous puissiez avoir:

    Action myField;

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

Utilisation dans l'interface utilisateur (WinForms, WPF, UWP, etc.)

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’ils peuvent nous donner leurs méthodes à référencer depuis notre délégué, et que nous sommes un bouton d’interface utilisateur. peut demander à toute personne intéressée par un clic sur moi, d'enregistrer sa méthode auprès de nous (via l'événement 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 arrive et clique sur ce bouton, nous aurons alors suffisamment de raisons d'appeler le délégué. Et parce que le délégué fait référence à toutes les méthodes qui nous ont été données, toutes ces méthodes seront invoquées. Nous ne savons pas ce que font ces méthodes, ni quelle classe implémente ces méthodes. Tout ce qui nous importe, c’est que quelqu'un souhaitait que l'on clique sur nous et nous donne une référence à une méthode conforme à la signature souhaitée.

Java

Les langues comme Java n'ont pas de délégués. Ils utilisent des interfaces à la place. Pour ce faire, ils demandent à quiconque est intéressé par "nous sommes cliqués", d'implémenter une certaine interface (avec une certaine méthode que nous pouvons appeler), puis de nous donner l'intégralité de l'instance qui implémente l'interface. Nous conservons une liste de tous les objets implémentant cette interface et pouvons appeler leur «méthode certaine, nous pouvons appeler» chaque fois que nous cliquons.

93
tofi9

Voici un exemple de code qui peut 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();
    }
  }
}
38
Gary Willoughby

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, écrivez quelque chose comme ceci:

public class Foo
{
    public event EventHandler MyEvent;
}

Et ensuite, vous pouvez vous inscrire à 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 Foo déclenche MyEvent, votre gestionnaire OnMyEvent sera appelé.

Il n'est pas toujours nécessaire d'utiliser une instance de EventArgs en tant que second paramètre. Si vous souhaitez inclure des informations supplémentaires, vous pouvez utiliser une classe dérivée de EventArgs (EventArgs est la base par convention). Par exemple, si vous examinez certains des événements définis dans Control dans WinForms ou FrameworkElement dans WPF, vous pouvez voir des exemples d'événements qui transmettent des informations supplémentaires aux gestionnaires d'événements.

33
Andy

Juste pour ajouter aux bonnes réponses existantes ici - en s’appuyant sur le code du code accepté, qui utilise la fonction delegate void MyEventHandler(string foo)...

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

myObj.SomethingHappened += HandleSomethingHappened;

Est totalement équivalent à:

myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened);

Et les gestionnaires peuvent également être non enregistrés avec -= comme ceci:

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

Par souci d’intégralité, le relèvement de l’événement peut se faire de la manière suivante, uniquement dans la classe qui en est propriétaire:

//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 sécurisée pour le thread - sinon un thread pourrait annuler l'enregistrement du dernier gestionnaire pour l'événement immédiatement après avoir vérifié si elle était null et nous aurions un "plaisir". NullReferenceException là-bas.


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

SomethingHappened?.Invoke("Hi there!");
21
Mathieu Guindon

Ma compréhension des événements est;

Délégué:  

Une variable pour contenir une référence à une méthode/à des 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. Comme un événement est une instance d'un délégué, nous devons d'abord définir le délégué.

  3. Assigne la/les méthodes à exécuter lorsque l'événement est déclenché (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();
            }
        }

    }   

}
11
KE50

éditeur: où les événements se produisent. Publisher doit spécifier le délégué que la classe utilise et générer les arguments nécessaires, puis passer 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é reçoit certains arguments d'événement (données, etc.), mais l'éditeur n'a aucune idée de ce qui va se passer 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 d'éditeur, afin que les abonnés puissent répondre aux événements de l'éditeur.

3
rileyss
//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);
2
Bilgi Sayar

Je suis d'accord avec KE50 sauf que je considère le mot clé 'event' comme un alias pour 'ActionCollection' car l'événement contient un ensemble d'actions à exécuter (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();
        }
    }

}   

}
0
user3902302

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

L'une des principales raisons pour lesquelles de nouvelles fonctionnalités apparaissent dans les langues et dans 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 vaut aussi pour certains délégués et événements! Je les trouve utiles et ajoute de la valeur au langage C #, mais d'autre part, le langage Java a décidé de ne pas les utiliser! ils ont décidé que tout ce que vous résolvez avec des délégués peut déjà être résolu avec les fonctionnalités existantes du langage, à savoir les interfaces, par exemple 

Vers 2001, Microsoft a publié le framework .NET et le langage C # en tant que solution concurrente de Java. Il était donc intéressant d’avoir de NOUVELLES FONCTIONNALITÉS que Java n’a pas.

0
Siraf

J'ai récemment créé un exemple d'utilisation des événements en c # et 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 une description et un code source (avec de nombreux commentaires), et se concentre principalement sur une utilisation appropriée (comme un modèle) d’événements et de gestionnaires d’événements.

Quelques points clés sont:

  • Les événements sont comme des "sous-types de délégués", seulement plus limités (dans le bon sens). En fait, une déclaration d'événement inclut toujours un délégué (les gestionnaires d'événement 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 ayant une "signature" spécifique. La signature est au format: (expéditeur de l'objet, arguments de l'événement EventArgs).

  • Vous pouvez créer votre propre sous-classe EventArgs, afin d'inclure tout type d'informations que l'événement doit véhiculer. 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 essentielle 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 comme publics. C'est une distinction très importante, car elle permet d'exposer vos événements de sorte qu'ils soient "connectés" à des méthodes externes, tout en les protégeant contre les "utilisations abusives externes".

0
konsfik