Quelles sont les différences entre les délégués et un événement? Les deux ne contiennent-elles pas de références à des fonctions qui peuvent être exécutées?
Quelles sont les différences entre les délégués et un événement? Les deux ne contiennent-elles pas de références à des fonctions qui peuvent être exécutées?
Réponses:
Une déclaration d' événement ajoute une couche d'abstraction et de protection sur l' instance déléguée . Cette protection empêche les clients du délégué de réinitialiser le délégué et sa liste d'appels et autorise uniquement l'ajout ou la suppression de cibles de la liste d'appels.
Pour comprendre les différences, vous pouvez regarder ces 2 exemples
Exemple avec des délégués (dans ce cas, une action - c'est une sorte de délégué qui ne renvoie pas de valeur)
public class Animal
{
public Action Run {get; set;}
public void RaiseEvent()
{
if (Run != null)
{
Run();
}
}
}
Pour utiliser le délégué, vous devez faire quelque chose comme ceci:
Animal animal= new Animal();
animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running") ;
animal.RaiseEvent();
Ce code fonctionne bien mais vous pourriez avoir des points faibles.
Par exemple, si j'écris ceci:
animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running");
animal.Run = () => Console.WriteLine("I'm sleeping") ;
avec la dernière ligne de code, j'ai remplacé les comportements précédents juste avec un manquant +
(j'ai utilisé à la =
place de +=
)
Un autre point faible est que chaque classe qui utilise votre Animal
classe peut augmenter RaiseEvent
simplement en l'appelant animal.RaiseEvent()
.
Pour éviter ces points faibles, vous pouvez utiliser events
en c #.
Votre classe d'animaux va changer de cette façon:
public class ArgsSpecial : EventArgs
{
public ArgsSpecial (string val)
{
Operation=val;
}
public string Operation {get; set;}
}
public class Animal
{
// Empty delegate. In this way you are sure that value is always != null
// because no one outside of the class can change it.
public event EventHandler<ArgsSpecial> Run = delegate{}
public void RaiseEvent()
{
Run(this, new ArgsSpecial("Run faster"));
}
}
appeler des événements
Animal animal= new Animal();
animal.Run += (sender, e) => Console.WriteLine("I'm running. My value is {0}", e.Operation);
animal.RaiseEvent();
Différences:
Remarques:
EventHandler est déclaré en tant que délégué suivant:
public delegate void EventHandler (object sender, EventArgs e)
il prend un expéditeur (de type Object) et des arguments d'événement. L'expéditeur est nul s'il provient de méthodes statiques.
Cet exemple, qui utilise EventHandler<ArgsSpecial>
, peut également être écrit en utilisant à la EventHandler
place.
Reportez-vous ici pour la documentation sur EventHandler
RaiseEvent
tant qu'une méthode d'appel a accès à une instance de animal
dans le code qui utilise l'événement?
animal.Run(this, new ArgsSpecial("Run faster");
?
En plus des propriétés syntaxiques et opérationnelles, il existe également une différence sémantique.
Les délégués sont, conceptuellement, des modèles de fonctions; c'est-à-dire qu'ils expriment un contrat auquel une fonction doit adhérer pour être considérée comme le «type» du délégué.
Les événements représentent ... eh bien, les événements. Ils sont destinés à alerter quelqu'un quand quelque chose se passe et oui, ils adhèrent à une définition de délégué mais ce n'est pas la même chose.
Même s'ils étaient exactement la même chose (syntaxiquement et dans le code IL), il restera toujours la différence sémantique. En général, je préfère avoir deux noms différents pour deux concepts différents, même s'ils sont implémentés de la même manière (ce qui ne veut pas dire que j'aime avoir le même code deux fois).
Voici un autre bon lien à consulter. http://csharpindepth.com/Articles/Chapter2/Events.aspx
En bref, le point à retenir de l'article - Les événements sont l'encapsulation des délégués.
Citation de l'article:
Supposons que les événements n'existent pas en tant que concept dans C # /. NET. Comment une autre classe s'inscrirait-elle à un événement? Trois options:
Une variable déléguée publique
Une variable déléguée soutenue par une propriété
Une variable déléguée avec les méthodes AddXXXHandler et RemoveXXXHandler
L'option 1 est clairement horrible, pour toutes les raisons normales pour lesquelles nous détestons les variables publiques.
L'option 2 est légèrement meilleure, mais permet aux abonnés de se remplacer mutuellement - il serait trop facile d'écrire someInstance.MyEvent = eventHandler; qui remplacerait tous les gestionnaires d'événements existants plutôt que d'en ajouter un nouveau. De plus, vous devez toujours écrire les propriétés.
L'option 3 est fondamentalement ce que les événements vous donnent, mais avec une convention garantie (générée par le compilateur et soutenue par des drapeaux supplémentaires dans l'IL) et une implémentation "gratuite" si vous êtes satisfait de la sémantique que les événements de type champ vous donnent. L'abonnement aux événements et leur désabonnement sont encapsulés sans permettre un accès arbitraire à la liste des gestionnaires d'événements, et les langues peuvent simplifier les choses en fournissant une syntaxe pour la déclaration et l'abonnement.
public Delegate
variable exposerait des "données", mais au meilleur de ma connaissance, la POO n'a jamais mentionné aucun concept comme un Delegate
(ce n'est ni un "objet" ni un "message") et .NET traite à peine les délégués comme des données de toute façon.
AddXXXHandler
méthodes avec une private Delegate
variable peut être une bonne option. Dans ce cas, vous pouvez vérifier si un gestionnaire est déjà défini et réagir de manière appropriée. Cela peut également être une bonne configuration si vous avez besoin de l'objet contenant le Delegate
pour pouvoir effacer tous les gestionnaires ( event
ne vous donne aucun moyen de le faire).
REMARQUE: si vous avez accès à C # 5.0 Unleashed , lisez les "Limitations sur l'utilisation simple des délégués" dans le chapitre 18 intitulé "Événements" pour mieux comprendre les différences entre les deux.
Cela m'aide toujours à avoir un exemple simple et concret. Voici donc un pour la communauté. Je montre d'abord comment vous pouvez utiliser les délégués seuls pour faire ce que les événements font pour nous. Ensuite, je montre comment la même solution fonctionnerait avec une instance de EventHandler
. Et puis j'explique pourquoi nous NE voulons PAS faire ce que j'explique dans le premier exemple. Ce billet est inspiré d' un article de John Skeet.
Exemple 1: utilisation d'un délégué public
Supposons que j'ai une application WinForms avec une seule liste déroulante. La liste déroulante est liée à un List<Person>
. Où Personne a les propriétés Id, Name, NickName, HairColor. Sur le formulaire principal se trouve un contrôle utilisateur personnalisé qui affiche les propriétés de cette personne. Lorsque quelqu'un sélectionne une personne dans la liste déroulante, les étiquettes de la mise à jour du contrôle utilisateur affichent les propriétés de la personne sélectionnée.
Voici comment cela fonctionne. Nous avons trois fichiers qui nous aident à mettre cela ensemble:
Voici le code pertinent pour chacune des classes:
class Mediator
{
public delegate void PersonChangedDelegate(Person p); //delegate type definition
public static PersonChangedDelegate PersonChangedDel; //delegate instance. Detail view will "subscribe" to this.
public static void OnPersonChanged(Person p) //Form1 will call this when the drop-down changes.
{
if (PersonChangedDel != null)
{
PersonChangedDel(p);
}
}
}
Voici notre contrôle utilisateur:
public partial class DetailView : UserControl
{
public DetailView()
{
InitializeComponent();
Mediator.PersonChangedDel += DetailView_PersonChanged;
}
void DetailView_PersonChanged(Person p)
{
BindData(p);
}
public void BindData(Person p)
{
lblPersonHairColor.Text = p.HairColor;
lblPersonId.Text = p.IdPerson.ToString();
lblPersonName.Text = p.Name;
lblPersonNickName.Text = p.NickName;
}
}
Enfin, nous avons le code suivant dans notre Form1.cs. Ici, nous appelons OnPersonChanged, qui appelle tout code souscrit au délégué.
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Mediator.OnPersonChanged((Person)comboBox1.SelectedItem); //Call the mediator's OnPersonChanged method. This will in turn call all the methods assigned (i.e. subscribed to) to the delegate -- in this case `DetailView_PersonChanged`.
}
D'accord. Voilà donc comment vous pourriez faire fonctionner cela sans utiliser d'événements et simplement utiliser des délégués . Nous venons de mettre un délégué public dans une classe - vous pouvez le rendre statique ou un singleton, ou autre chose. Génial.
MAIS, MAIS, MAIS, nous ne voulons pas faire ce que je viens de décrire ci-dessus. Parce que les champs publics sont mauvais pour de nombreuses raisons. Quelles sont nos options? Comme John Skeet le décrit, voici nos options:
PersonChangedDel = null
, effacer tous les autres abonnements. un autre problème qui reste ici est que puisque les utilisateurs ont accès au délégué, ils peuvent invoquer les cibles dans la liste d'invocation - nous ne voulons pas que les utilisateurs externes aient accès au moment de déclencher nos événements.Cette troisième option est essentiellement ce que nous offre un événement. Lorsque nous déclarons un EventHandler, cela nous donne accès à un délégué - pas publiquement, pas en tant que propriété, mais comme cette chose, nous appelons un événement qui vient d'ajouter / supprimer des accesseurs.
Voyons à quoi ressemble le même programme, mais en utilisant maintenant un événement au lieu du délégué public (j'ai également changé notre médiateur en singleton):
Exemple 2: avec EventHandler au lieu d'un délégué public
Médiateur:
class Mediator
{
private static readonly Mediator _Instance = new Mediator();
private Mediator() { }
public static Mediator GetInstance()
{
return _Instance;
}
public event EventHandler<PersonChangedEventArgs> PersonChanged; //this is just a property we expose to add items to the delegate.
public void OnPersonChanged(object sender, Person p)
{
var personChangedDelegate = PersonChanged as EventHandler<PersonChangedEventArgs>;
if (personChangedDelegate != null)
{
personChangedDelegate(sender, new PersonChangedEventArgs() { Person = p });
}
}
}
Notez que si vous F12 sur le EventHandler, il vous montrera que la définition est juste un délégué générique-ified avec l'objet supplémentaire "expéditeur":
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
Le contrôle utilisateur:
public partial class DetailView : UserControl
{
public DetailView()
{
InitializeComponent();
Mediator.GetInstance().PersonChanged += DetailView_PersonChanged;
}
void DetailView_PersonChanged(object sender, PersonChangedEventArgs e)
{
BindData(e.Person);
}
public void BindData(Person p)
{
lblPersonHairColor.Text = p.HairColor;
lblPersonId.Text = p.IdPerson.ToString();
lblPersonName.Text = p.Name;
lblPersonNickName.Text = p.NickName;
}
}
Enfin, voici le code Form1.cs:
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Mediator.GetInstance().OnPersonChanged(this, (Person)comboBox1.SelectedItem);
}
Parce que EventHandler veut et EventArgs comme paramètre, j'ai créé cette classe avec juste une seule propriété:
class PersonChangedEventArgs
{
public Person Person { get; set; }
}
J'espère que cela vous montre un peu pourquoi nous avons des événements et comment ils sont différents - mais fonctionnellement les mêmes - que les délégués.
The other problem that remains here is that since the users have access to the delegate, they can invoke the targets in the invocation list -- we don't want external users having access to when to raise our events
. Dans la dernière version de Mediator
, vous pouvez toujours appeler le OnPersonChange
chaque fois que vous avez une référence au singleton. Vous devriez peut-être mentionner que l' Mediator
approche n'empêche pas ce comportement particulier et est plus proche d'un bus d'événements.
Vous pouvez également utiliser des événements dans les déclarations d'interface, pas pour les délégués.
Action a { get; set; }
intérieur d'une définition d'interface.
Quel grand malentendu entre les événements et les délégués !!! Un délégué spécifie un TYPE (tel qu'un class
, ou un interface
fait), tandis qu'un événement est juste une sorte de MEMBRE (tel que des champs, des propriétés, etc.). Et, comme tout autre type de membre, un événement a également un type. Pourtant, dans le cas d'un événement, le type de l'événement doit être spécifié par un délégué. Par exemple, vous NE POUVEZ PAS déclarer un événement d'un type défini par une interface.
En conclusion, nous pouvons faire l' observation suivante : le type d'événement DOIT être défini par un délégué . Il s'agit de la relation principale entre un événement et un délégué et est décrite dans la section II.18 Définition des événements des partitions I à VI de l' ECMA-335 (CLI) :
Dans une utilisation standard, le TypeSpec (s'il est présent) identifie un délégué dont la signature correspond aux arguments passés à la méthode de déclenchement de l'événement.
Cependant, ce fait n'implique PAS qu'un événement utilise un champ délégué de sauvegarde . En vérité, un événement peut utiliser un champ de support de tout type de structure de données différent de votre choix. Si vous implémentez un événement explicitement en C #, vous êtes libre de choisir la façon dont vous stockez les gestionnaires d'événements (notez que les gestionnaires d'événements sont des instances du type de l'événement , qui à son tour est obligatoirement un type délégué --- de l' observation précédente ). Mais, vous pouvez stocker ces gestionnaires d'événements (qui sont des instances de délégué) dans une structure de données telle que a List
ou a Dictionary
ou toute autre autre, ou même dans un champ délégué de sauvegarde. Mais n'oubliez pas qu'il n'est PAS obligatoire d'utiliser un champ délégué.
Un événement dans .net est une combinaison désignée d'une méthode Add et d'une méthode Remove, qui attendent toutes les deux un type particulier de délégué. C # et vb.net peuvent tous deux générer automatiquement du code pour les méthodes d'ajout et de suppression qui définiront un délégué pour conserver les abonnements aux événements, et ajouteront / supprimeront les délégués transmis à / de ce délégué d'abonnement. VB.net générera également du code automatiquement (avec l'instruction RaiseEvent) pour appeler la liste d'abonnement si et seulement si elle n'est pas vide; pour une raison quelconque, C # ne génère pas ce dernier.
Notez que bien qu'il soit courant de gérer les abonnements aux événements à l'aide d'un délégué de multidiffusion, ce n'est pas le seul moyen de le faire. D'un point de vue public, un abonné potentiel à un événement doit savoir comment faire savoir à un objet qu'il souhaite recevoir des événements, mais il n'a pas besoin de savoir quel mécanisme l'éditeur utilisera pour déclencher les événements. Notez également que même si celui qui a défini la structure des données d'événement dans .net a apparemment pensé qu'il devrait y avoir un moyen public de les augmenter, ni C # ni vb.net n'utilisent cette fonctionnalité.
Pour définir un événement de manière simple:
L'événement est une RÉFÉRENCE à un délégué avec deux restrictions
Au-dessus de deux, les points faibles des délégués sont abordés en cas d'événement. L'échantillon de code complet pour montrer la différence dans le violon est ici https://dotnetfiddle.net/5iR3fB .
Basculez le commentaire entre Event et Delegate et le code client qui invoque / attribue des valeurs à déléguer pour comprendre la différence
Voici le code en ligne.
/*
This is working program in Visual Studio. It is not running in fiddler because of infinite loop in code.
This code demonstrates the difference between event and delegate
Event is an delegate reference with two restrictions for increased protection
1. Cannot be invoked directly
2. Cannot assign value to delegate reference directly
Toggle between Event vs Delegate in the code by commenting/un commenting the relevant lines
*/
public class RoomTemperatureController
{
private int _roomTemperature = 25;//Default/Starting room Temperature
private bool _isAirConditionTurnedOn = false;//Default AC is Off
private bool _isHeatTurnedOn = false;//Default Heat is Off
private bool _tempSimulator = false;
public delegate void OnRoomTemperatureChange(int roomTemperature); //OnRoomTemperatureChange is a type of Delegate (Check next line for proof)
// public OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above),
public event OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above),
public RoomTemperatureController()
{
WhenRoomTemperatureChange += InternalRoomTemperatuerHandler;
}
private void InternalRoomTemperatuerHandler(int roomTemp)
{
System.Console.WriteLine("Internal Room Temperature Handler - Mandatory to handle/ Should not be removed by external consumer of ths class: Note, if it is delegate this can be removed, if event cannot be removed");
}
//User cannot directly asign values to delegate (e.g. roomTempControllerObj.OnRoomTemperatureChange = delegateMethod (System will throw error)
public bool TurnRoomTeperatureSimulator
{
set
{
_tempSimulator = value;
if (value)
{
SimulateRoomTemperature(); //Turn on Simulator
}
}
get { return _tempSimulator; }
}
public void TurnAirCondition(bool val)
{
_isAirConditionTurnedOn = val;
_isHeatTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary)
System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn);
System.Console.WriteLine("Heat :" + _isHeatTurnedOn);
}
public void TurnHeat(bool val)
{
_isHeatTurnedOn = val;
_isAirConditionTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary)
System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn);
System.Console.WriteLine("Heat :" + _isHeatTurnedOn);
}
public async void SimulateRoomTemperature()
{
while (_tempSimulator)
{
if (_isAirConditionTurnedOn)
_roomTemperature--;//Decrease Room Temperature if AC is turned On
if (_isHeatTurnedOn)
_roomTemperature++;//Decrease Room Temperature if AC is turned On
System.Console.WriteLine("Temperature :" + _roomTemperature);
if (WhenRoomTemperatureChange != null)
WhenRoomTemperatureChange(_roomTemperature);
System.Threading.Thread.Sleep(500);//Every second Temperature changes based on AC/Heat Status
}
}
}
public class MySweetHome
{
RoomTemperatureController roomController = null;
public MySweetHome()
{
roomController = new RoomTemperatureController();
roomController.WhenRoomTemperatureChange += TurnHeatOrACBasedOnTemp;
//roomController.WhenRoomTemperatureChange = null; //Setting NULL to delegate reference is possible where as for Event it is not possible.
//roomController.WhenRoomTemperatureChange.DynamicInvoke();//Dynamic Invoke is possible for Delgate and not possible with Event
roomController.SimulateRoomTemperature();
System.Threading.Thread.Sleep(5000);
roomController.TurnAirCondition (true);
roomController.TurnRoomTeperatureSimulator = true;
}
public void TurnHeatOrACBasedOnTemp(int temp)
{
if (temp >= 30)
roomController.TurnAirCondition(true);
if (temp <= 15)
roomController.TurnHeat(true);
}
public static void Main(string []args)
{
MySweetHome home = new MySweetHome();
}
}
Le délégué est un pointeur de fonction de type sécurisé. L'événement est une implémentation du modèle de conception éditeur-abonné utilisant délégué.
Si vous cochez Intermediate Language, vous saurez que le compilateur .net convertit le délégué en une classe scellée en IL avec certaines fonctions intégrées, comme invoke, beginInvoke, endInvoke et la classe déléguée héritée d'une autre classe, peut-être appelée "SystemMulticast". Je suppose que Event est une classe enfant de Delegate avec des propriétés supplémentaires.
La différence entre l'instance de l'événement et le délégué est que vous ne pouvez pas exécuter l'événement en dehors de la déclaration. Si vous déclarez un événement dans la classe A, vous pouvez uniquement exécuter cet événement dans la classe A. Si vous déclarez un délégué dans la classe A, vous pouvez utiliser ce délégué n'importe où. Je pense que c'est la principale différence entre eux