2008-08-26 8 views

Répondre

228

Une déclaration événement ajoute une couche d'abstraction et de protection sur le délégué exemple. Cette protection empêche les clients du délégué de réinitialiser le délégué et sa liste d'appel et autorise uniquement l'ajout ou la suppression de cibles de la liste d'appel.

+35

Si cours , cette couche de protection empêche également les "clients" (code en dehors de la classe/structure de définition) d'invoquer le délégué, et d'obtenir de quelque manière que ce soit l'objet délégué "derrière" l'événement. –

+2

.... Et la protection sur l'instance de délégué ** variable ** –

+5

Pas tout à fait vrai. Vous pouvez déclarer un événement sans instance de délégué backend. Dans C#, vous pouvez implémenter un événement explicitement et utiliser une structure de données backend différente de votre choix. –

87

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 fonction; c'est-à-dire qu'ils expriment un contrat auquel une fonction doit adhérer pour être considéré comme le «type» du délégué.

Les événements représentent ... bien, les événements. Ils sont destinés à alerter quelqu'un quand quelque chose se produit et oui, ils adhèrent à une définition de délégué, mais ce n'est pas la même chose.

Même si elles étaient exactement la même chose (syntaxiquement et dans le code IL) il resterait 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).

+8

Excellente description des délégués. – Sampson

+0

Donc, pourrions-nous dire qu'un événement est un type "spécial" d'un délégué? – Pap

7

Vous pouvez également utiliser des événements dans les déclarations d'interface, pas pour les délégués.

+2

@surfen L'interface peut contenir des événements, mais pas des délégués. –

+0

Que voulez-vous dire exactement? Vous pouvez avoir 'Action a {get; ensemble; } 'dans une définition d'interface. –

5

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 deux un type particulier de délégué. C# et vb.net peuvent générer automatiquement du code pour les méthodes add et remove qui définiront un délégué pour contenir les abonnements aux événements, et ajouter/supprimer le délégué dans/de ce délégué d'abonnement. VB.net générera aussi automatiquement du code (avec l'instruction RaiseEvent) pour invoquer la liste d'abonnement si et seulement si elle est non vide; pour une raison quelconque, C# ne génère pas ce dernier. Notez que s'il est courant de gérer des abonnements à un événement à 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é à un événement potentiel 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 si quiconque a défini la structure de données d'événement dans .net pensait apparemment qu'il devrait y avoir un moyen public de les élever, ni C# ni vb.net n'utilisent cette fonctionnalité.

35

Il est un ancien poste, mais si quelqu'un trébuche sur lui, comme je l'ai fait - voici un autre bon lien pour désigner .. http://csharpindepth.com/Articles/Chapter2/Events.aspx

brièvement, la prennes de l'article - Les événements sont l'encapsulation sur délégués. Citation de l'article -..

« événements Supposons que n'existait pas comme un concept en C#/NET Comment se une autre classe abonnez-vous à un événement?

Trois options:

  1. variable publique délégué delegatevariable

  2. soutenu par une propriété

  3. variable délégué des méthodes AddXXXHandler et RemoveXXXHandler

Option 1 est clairement horrible, fo r toutes les raisons normales nous abhorrons variables publiques.

L'option 2 est meilleure, mais permet aux abonnés de surcharger effectivement l'un l'autre - 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. En outre, vous devez toujours écrire les propriétés .

Option 3 est essentiellement ce que les événements vous donnent, mais avec une convention garantie (généré par le compilateur et soutenu par des drapeaux supplémentaires dans le IL) et une mise en œuvre « libre » si vous êtes satisfait de la sémantique que les événements de type champ vous donnent. Vous inscrire et désabonnement de événements est encapsulées sans permettre l'accès arbitraire à la liste des gestionnaires d'événements, et les langues peuvent rendre les choses plus simples en fournissant syntaxe pour les deux déclaration et abonnement. »

+0

Explication belle et concise. Thanx – Pap

69

Pour comprendre les différences que vous pouvez regardez ce 2 exemples

Exemple avec les délégués (dans ce cas, une action - qui est une sorte de délégué qui ne renvoie pas une valeur)

public class Animal 
{ 
    public Action Run {get; set;} 

    public void RaiseEvent() 
    { 
     if (Run != null) 
     { 
      Run(); 
     } 
    } 
} 

Pour nous e le délégué, vous devriez 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 quelques 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, je l'ai substituée les comportements précédents seulement avec un manque + (je l'ai utilisé = au lieu de +=)

Un autre point faible est que chaque classe qui utilise votre classe Animal peut lever RaiseEvent en l'appelant simplement animal.RaiseEvent(). Pour éviter ces points faibles, vous pouvez utiliser events en C#.

Votre classe Animal changera 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")); 
    } 
} 

aux événements appellent

Animal animal= new Animal(); 
animal.Run += (sender, e) => Console.WriteLine("I'm running. My value is {0}", e.Operation); 
animal.RaiseEvent(); 

Différences:

  1. Vous n'utilisez pas un bien public, mais un champ public (en utilisant des événements, le compilateur protège vos champs de l'accès non désiré)
  2. Les événements ne peuvent pas être affectés directement. Dans ce cas, cela ne donnera pas lieu à l'erreur précédente que j'ai montrée en remplaçant le comportement.
  3. Personne en dehors de votre classe ne peut élever l'événement.
  4. Les événements peuvent être inclus dans une déclaration d'interface, alors qu'un champ ne peut pas

Notes:

EventHandler est déclarée en tant que délégué suivant:

public delegate void EventHandler (object sender, EventArgs e) 

prend un expéditeur (de l'objet type) et les 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 EventHandler à la place.

Référez-here pour la documentation sur EventHandler

+3

Tout avait l'air bien jusqu'à ce que je rencontre "Personne en dehors de votre classe ne peut soulever l'événement." Qu'est-ce que ça veut dire? Quelqu'un peut-il appeler 'RaiseEvent' tant qu'une méthode appelante a un accès à une instance de' animal' dans le code qui utilise l'événement? – Sung

+6

@Sung Les événements peuvent seulement être levés depuis l'intérieur de la classe, peut-être que je n'ai pas été clair expliquant cela. Avec les événements, vous pouvez appeler la fonction qui déclenche l'événement (encapsulation), mais elle ne peut être levée qu'à l'intérieur de la classe qui la définit. Faites-moi savoir si je ne suis pas clair. – faby

+1

Bonne réponse! Continuez votre bon travail :-) –

6

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), alors qu'un événement est juste une sorte de MEMBRE (comme des champs, des propriétés, etc.). Et, comme tout autre type de membre, un événement a aussi un type. Cependant, 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, on peut faire ce qui suit Observation: le type d'événement DOIT être défini par un délégué. Ceci est la relation principale entre un événement et un délégué et est décrit dans la section II.18 événements Définition de ECMA-335 (CLI) Partitions I to VI:

En utilisation typique, le TypeSpec (le cas échéant) identifie un délégué dont la signature correspond aux arguments transmis à la méthode de feu de l'événement.

Cependant, ce fait ne signifie pas qu'un événement utilise un délégué soutien champ. En réalité, un événement peut utiliser un champ de support de n'importe quel 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 gestionnaires d'événements sont les instances du type de l'événement, qui à son tour est obligatoirement un type délégué --- du précédent Observation). Mais, vous pouvez stocker ces gestionnaires d'événements (qui sont des instances de délégué) dans une structure de données telle qu'un List ou un Dictionary ou tout autre, ou même dans un champ de délégué de support. Mais n'oubliez pas qu'il n'est PAS obligatoire d'utiliser un champ délégué.

6

REMARQUE: Si vous avez accès à C# 5.0 Unleashed, lisez la section «Limitations sur l'utilisation normale des délégués» du chapitre 18 intitulée «Événements» pour mieux comprendre les différences entre les deux. Cela m'aide toujours à avoir un exemple simple et concret. Alors, voici 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. Cet article a été inspiré par an article par John Skeet.

Exemple 1: Utilisation 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 de Id, Name, NickName, HairColor. Sur le formulaire principal est un contrôle utilisateur personnalisé qui montre les propriétés de cette personne. Lorsque quelqu'un sélectionne une personne dans la liste déroulante, les libellés de la mise à jour du contrôle utilisateur indiquent les propriétés de la personne sélectionnée.

enter image description here

Voici comment cela fonctionne. Nous avons trois fichiers qui nous aident à mettre cela ensemble:

  • Mediator.cs - classe statique détient les délégués
  • Form1.cs - forme principale
  • de DetailView.cs - contrôle utilisateur affiche tous les détails

Voici le code correspondant 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 abonné 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`. 
} 

Ok. C'est ainsi que vous obtiendrez ce travail sans utiliser les événements et simplement en utilisant les délégués. Nous mettons juste un délégué public dans une classe - vous pouvez le rendre statique ou un singleton, ou peu importe. Génial.

MAIS, MAIS, MAIS, nous ne voulons pas faire ce que je viens de décrire ci-dessus. Parce que public fields are bad pour beaucoup, beaucoup de raisons. Quelles sont nos options?Comme John Skeet décrit, voici les options:

  1. Une variable délégué du public (.. Ce que nous avons fait juste au-dessus ne fait pas ce que je viens de vous dire ci-dessus pourquoi il est mauvais)
  2. Mettre le délégué dans une propriété avec un get/set (le problème ici est que les abonnés peuvent se remplacer mutuellement - ainsi nous pourrions souscrire un tas de méthodes au délégué et ensuite nous pourrions accidentellement dire PersonChangedDel = null, effaçant tous les autres abonnements. cela 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 à quand élever nos événements
  3. Une variable délégué des méthodes AddXXXHandler et RemoveXXXHandler

Cette troisième option est essentiellement ce un événement nous donne. Lorsque nous déclarons un EventHandler, il nous donne accès à un délégué - pas publiquement, pas comme une propriété, mais comme cette chose que nous appelons un événement qui a juste ajouter/supprimer des accesseurs.

Voyons voir ce que le même programme ressemble, mais en utilisant un événement au lieu du délégué du public (je l'ai aussi changé notre médiateur à un singleton):

Exemple 2: Avec EventHandler au lieu d'un délégué du public

Mediator:

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 gestionnaire d'événements, il vous montrera 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 de l'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 le EventHandler veut et EventArgs en tant que paramètre, j'ai créé cette classe avec une seule propriété en elle:

class PersonChangedEventArgs 
{ 
    public Person Person { get; set; } 
} 

Espérons que vous montre un peu pourquoi nous avons des événements et comment ils sont différents - mais fonctionnellement identiques - en tant que délégués.

+0

Bien que j'apprécie Tout le bon travail dans ce post et j'ai aimé lire la plupart de celui-ci, je pense toujours un problème n'est pas adressé - L'autre problème qui reste ici est que puisque les utilisateurs ont accès au délégué, ils peuvent invoquer les cibles dans l'invocation liste - nous ne voulons pas que les utilisateurs externes aient accès à quand élever nos événements. »Dans la dernière version du' Mediator', vous pouvez toujours appeler 'OnPersonChange' quand vous avez une référence au singleton. mentionner que tha L'approche «Mediator» n'empêche pas ce comportement particulier et se rapproche d'un bus d'événements. –

3

Pour définir sur l'événement de manière simple:

événement est une REFERENCE à un délégué avec deux restrictions

  1. ne peut être invoquée directement
  2. Ne peut pas être affectées directement des valeurs (par exemple eventObj = delegateMethod)

Au-dessus de t wo sont les points faibles pour les délégués et il est adressé dans l'événement. Échantillon de code complet pour montrer la différence dans le violoniste est ici https://dotnetfiddle.net/5iR3fB.

Bascule le commentaire entre l'événement et délégué et le code client qui appelle/attribuer des valeurs à déléguer à 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(); 
    } 


} 
Questions connexes