Si les événements sont implémentés en tant que délégués dans .NET, à quoi sert la section .event IL?
Question
J'ai vu de très bonnes questions sur le dépassement de capacité concernant les délégués, les événements et la mise en œuvre .NET de ces deux fonctionnalités. Une question en particulier, " Comment les événements C # fonctionnent-ils derrière le scènes? ", a produit une excellente réponse qui explique très bien certains points subtils.
La réponse à la question ci-dessus fait ressortir ce point:
Lorsque vous déclarez un événement semblable à un champ ... le compilateur génère les méthodes et un champ privé (du même type en tant que délégué). Dans la classe, lorsque vous vous référez à ElementAddedEvent vous faites référence au terrain. À l'extérieur la classe, vous faites référence à la champ
Un article MSDN lié à la même question (& Événements de type champ. ") ajoute:
L’idée de faire un événement est précisément équivalent à invoquer la délégué représenté par l'événement & # 8212; ainsi, il n'y a pas de langue spéciale des constructions pour élever des événements
Désireux d’examiner plus en détail, j’ai construit un projet test afin de visualiser le livre final dans lequel un événement et un délégué sont compilés:
public class TestClass
{
public EventHandler handler;
public event EventHandler FooEvent;
public TestClass()
{ }
}
Je m'attendais à ce que le champ de délégué Comme les événements ne sont rien d'autre que des délégués avec les méthodes Mes dernières questions sont les suivantes: si les événements sont implémentés simplement en tant que délégués avec des méthodes d'accès, à quoi sert-il d'avoir une section IL gestionnaire
et l'événement FooEvent
soient compilés en gros au même code IL, avec quelques méthodes supplémentaires pour encapsuler l'accès au code .class public auto ansi beforefieldinit TestClass
extends [mscorlib]System.Object
{
.event [mscorlib]System.EventHandler FooEvent
{
.addon instance void TestClass::add_FooEvent(class [mscorlib]System.EventHandler)
.removeon instance void TestClass::remove_FooEvent(class [mscorlib]System.EventHandler)
}
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
// Constructor IL hidden
}
.field private class [mscorlib]System.EventHandler FooEvent
.field public class [mscorlib]System.EventHandler handler
}
add
et remove
générées par le compilateur, je ne m'attendais pas à voir les événements traités autrement que dans IL. Mais les méthodes d'ajout et de suppression sont définies dans une section commençant par .event
et non par .method
comme le sont les méthodes normales. .event
? Ne pourraient-ils pas être implémentés dans IL sans cela en utilisant les sections .method
? Est-ce que .event
est équivalent à .method
?
La solution
Je ne suis pas sûr que ce soit surprenant ... comparez-le pour les propriétés vs les champs (puisque les propriétés ont la même fonction que les événements: encapsulation via des accesseurs):
.field public string Foo // public field
.property instance string Bar // public property
{
.get instance string MyType::get_Bar()
.set instance void MyType::set_Bar(string)
}
De plus, les événements ne ne mentionnent rien à propos des champs; ils seulement définissent les accesseurs (ajout / suppression). Le délégué délégué est un détail de la mise en œuvre; il se trouve que des événements de type champ déclarent un champ en tant que membre de support - de la même manière que les propriétés implémentées automatiquement déclarent un champ en tant que membre de support. D'autres implémentations sont possibles (et très courantes, notamment dans les formulaires, etc.).
Autres implémentations courantes:
Evénements épars (contrôles, etc.) - EventHandlerList (ou similaire):
// only one instance field no matter how many events;
// very useful if we expect most events to be unsubscribed
private EventHandlerList events = new EventHandlerList();
protected EventHandlerList Events {
get { return events; } // usually lazy
}
// this code repeated per event
private static readonly object FooEvent = new object();
public event EventHandler Foo
{
add { Events.AddHandler(FooEvent, value); }
remove { Events.RemoveHandler(FooEvent, value); }
}
protected virtual void OnFoo()
{
EventHandler handler = Events[FooEvent] as EventHandler;
if (handler != null) handler(this, EventArgs.Empty);
}
(ce qui précède est à peu près la colonne vertébrale des événements win-forms)
Façade (bien que cela confonde un peu "l'expéditeur"; un code intermédiaire est souvent utile):
private Bar wrappedObject; // via ctor
public event EventHandler SomeEvent
{
add { wrappedObject.SomeOtherEvent += value; }
remove { wrappedObject.SomeOtherEvent -= value; }
}
(ce qui précède peut également être utilisé pour renommer efficacement un événement)
Autres conseils
Les événements ne sont pas les mêmes que les délégués. Les événements encapsulent l'ajout / la suppression d'un gestionnaire pour un événement. Le gestionnaire est représenté par un délégué.
Vous pourriez écrire simplement AddClickHandler / RemoveClickHandler, etc. pour chaque événement - mais cela serait relativement pénible et il ne serait pas facile pour des outils comme VS de séparer les événements de tout autre événement.
C’est vraiment comme les propriétés - vous pouvez écrire GetSize / SetSize, etc. (comme vous le faites en Java), mais en séparant les propriétés, il existe des raccourcis syntaxiques disponibles et une meilleure prise en charge des outils.
Le point d’avoir des événements qui constituent une paire de méthodes d’ajouts / de suppressions est la encapsulation .
La plupart des événements temporels sont utilisés tels quels, mais d'autres fois, vous ne souhaitez pas stocker les délégués attachés à l'événement dans un champ, ou vous souhaitez effectuer un traitement supplémentaire lors de l'ajout ou de la suppression de méthodes d'événement.
Par exemple, un moyen d'implémenter les événements économes en mémoire est de stocker les délégués dans un dictionnaire plutôt que un champ privé, car les champs sont toujours alloués, alors que la taille d'un dictionnaire augmente uniquement lorsque des éléments sont ajoutés. Ce modèle est similaire à ce que Winforms et WPF utilisent utilisent efficacement la mémoire (Winforms et WPF utilisent des dictionnaires de clé pour stocker les délégués et non les listes)