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 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?
9 réponses
Pour comprendre les gestionnaires d'événements, vous devez comprendre délégués. Dans C#, vous pouvez penser à un délégué comme un pointeur (ou une référence) à une méthode. Ceci est utile car le pointeur peut être passé 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 renvoient void
, et prennent un object
et EventArgs
. Type de 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é.
Sachant donc comment créer une référence à une méthode, pensons 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 être exécuté. La colle entre l'événement et les méthodes à exécuter sont les délégués. L'événement doit en interne stocker 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 transmettre! Nous utilisons le délégué, le "contrat" entre l'événement et toutes les méthodes qui seront appelées.
Donc, la valeur par défaut EventHandler
(et beaucoup l'aiment) représente une 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) cet événement appellera, 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 là où vit l'événement. Lorsque l'événement est "déclenché", c'est vraiment juste"passer par cette liste de méthodes et appeler chacune, en utilisant ces valeurs comme paramètres". Assigner un gestionnaire d'événements est juste un moyen plus joli et plus facile d'ajouter votre méthode à cela liste des méthodes à appeler).
C# connaît deux termes, delegate
et event
. Commençons avec la première.
Délégué
Un delegate
est une référence à une méthode. 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 par la référence:
MyClass instance = myMethod();
Mais pourquoi le feriez-vous? Vous pouvez également appeler directement myFactory.GetInstance()
. Dans ce cas, vous pouvez. Cependant, il y a beaucoup de cas où vous ne voulez pas que le reste de l'application connaisse myFactory
ou appelle myFactory.GetInstance()
directement.
Évidente est que si vous voulez être en mesure de remplacer myFactory.GetInstance()
dans myOfflineFakeFactory.GetInstance()
à partir d'un endroit central (aka usine méthode du modèle).
Modèle de méthode D'usine
Donc, si vous avez une classe TheOtherClass
et qu'elle doit utiliser le myFactory.GetInstance()
, Voici à quoi ressemblera le code sans délégués (vous devrez informer TheOtherClass
de la 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 et autres).
"Signature de ma méthode", Où ai-je entendu cela auparavant? O oui, interfaces!!! les interfaces décrivent la signature d'une classe entière. Pensez délégués décrivant la signature d'une seule méthode!
Une autre grande différence entre une interface et un délégué, c'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 quelques 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 pas avoir de valeur de retour (void
) et que les mots-clés out
et ref
ne sont pas utilisés dans la signature. Évidemment, vous ne pouvez pas appeler deux méthodes qui renvoient un nombre et vous attendre à ce qu'elles renvoient le même nombre. Une fois la signature conforme, le délégué est automatiquement un MulticastDelegate
.
Événement
Les événements ne sont que des propriétés (comme les champs get; set; properties to instance) qui exposent l'abonnement au délégué à partir d'autres objets. Ces propriétés, cependant, ne supportent pas get;set;. Au lieu de cela ils soutiennent ajouter;supprimer;
Donc vous pouvez avoir:
Action myField;
public event Action MyProperty
{
add { myField += value; }
remove { myField -= value; }
}
Utilisation dans L'interface utilisateur (WinForms)
Donc, maintenant nous savons qu'un délégué est une référence à une méthode et que l'on peut avoir d'un événement à faire savoir au monde qu'ils peuvent nous donner leurs méthodes pour être référencé à partir de notre Délégué, et nous sommes un bouton D'interface utilisateur, alors: nous pouvons demander à tous ceux qui sont intéressés à savoir si j'ai été cliqué, d'enregistrer leur 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, on attendra et on attendra.... jusqu'à ce qu'un utilisateur vienne et clique sur ce bouton, nous aurons 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 sont données, toutes ces méthodes seront être invoquée. Nous ne savons pas ce que font ces méthodes, ni quelle classe implémente ces méthodes. Tout ce qui nous intéresse, c'est que quelqu'un s'intéressait à ce que nous soyons cliqués et nous a donné une référence à une méthode qui respectait notre signature désirée.
Java
Les langages comme Java n'ont pas de délégués. Ils utilisent des interfaces à la place. La façon dont ils le font est de demander à quiconque est intéressé par "nous être cliqué", d'implémenter une certaine interface (avec une certaine méthode, nous pouvons call), puis donnez - nous toute l'instance 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 sommes cliqués.
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();
}
}
}
C'est en fait la déclaration pour 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 écrivez 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 Foo
déclenche MyEvent
, votre gestionnaire OnMyEvent
sera appelé.
Vous n'avez pas toujours besoin d'utiliser une instance de EventArgs
comme deuxième paramètre. Si vous voulez inclure des informations supplémentaires, vous pouvez utiliser une classe dérivée de EventArgs
(EventArgs
est la base d'une convention). Par exemple, si vous regardez certains des événements définis sur Control
en WinForms, ou FrameworkElement
dans WPF, vous pouvez voir des exemples d'événements qui passent de l'information supplémentaire pour les gestionnaires d'événements.
Juste pour ajouter aux bonnes réponses existantes ici - en s'appuyant sur le code dans le code accepté, qui utilise un delegate void MyEventHandler(string foo)
...
Parce 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 avec -=
comme ceci:
// -= removes the handler from the event's list of "listeners":
myObj.SomethingHappened -= HandleSomethingHappened;
Par souci d'exhaustivité, la levée de l'événement peut se faire comme ceci, seulement dans la classe qui possède le é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!");
}
Le fil-d'une copie locale du gestionnaire est nécessaire pour s'assurer que l'invocation est thread-safe, sinon un thread pourrait aller et annuler l'inscription de la dernière gestionnaire pour l'événement immédiatement après, nous avons vérifié si c'était null
, et nous aurions un "plaisir" NullReferenceException
il.
C # 6 a introduit une belle main courte pour ce motif. Il utilise l'opérateur de propagation null.
SomethingHappened?.Invoke("Hi there!");
Ma compréhension des événements est;
Délégué:
Une variable pour contenir la référence à la méthode / aux méthodes à exécuter. Cela permet de passer des méthodes comme une variable.
Étapes pour créer et appeler l'événement:
L'événement est une instance d'un délégué
Depuis un événement est une occurrence d'un délégué, alors nous devons d'abord définir le délégué.
Attribuer la / Les méthode (s) à exécuter lorsque l'événement est déclenché (Appeler le délégué)
Déclenche 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();
}
}
}
}
Editeur: où les événements se produisent. Publisher doit spécifier quel délégué la classe utilise et générer les arguments nécessaires, 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 devraient prendre le même type d'arguments que le délégué. Subscriber ajoute ensuite cette méthode au délégué de l'éditeur.
Par conséquent, lorsque l'événement se produit dans publisher, delegate recevra certains arguments d'événement (données, etc.), mais publisher 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 publisher, afin que les abonnés puissent répondre aux événements de publisher.
//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);
Je suis d'accord avec KE50 sauf que je considère le mot-clé 'event' comme un alias pour 'ActionCollection' puisque l'événement contient une collection d'actions à effectuer (ie. 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();
}
}
}
}