Question

Quelles sont les différences entre les délégués et un événement ?Les deux ne contiennent-ils pas des références à des fonctions qui peuvent être exécutées ?

Était-ce utile?

La solution

Un Événement La déclaration ajoute une couche d'abstraction et de protection sur le déléguer 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.

Autres conseils

Outre les 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 du « type » de délégué.

Les événements représentent...enfin, les événements.Ils sont destinés à alerter quelqu'un lorsque 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 s'il s'agissait exactement de la même chose (syntaxiquement et dans le code IL), la différence sémantique subsistera.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 deux fois le même code).

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 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, j'ai remplacé les comportements précédents avec un seul manquant + (J'ai utilisé = au lieu de +=)

Un autre point faible est que chaque classe qui utilise votre Animal la classe peut élever RaiseEvent je viens de l'appeler animal.RaiseEvent().

Pour éviter ces points faibles, vous pouvez utiliser events en c#.

Votre classe Animal changera de cette manière :

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"));
    }
}

convoquer 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:

  1. Vous n'utilisez pas une propriété publique mais un champ public (à l'aide d'événements, le compilateur protège vos champs des accès indésirables)
  2. Les événements ne peuvent pas être attribué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 déclencher l'événement.
  4. Les événements peuvent être inclus dans une déclaration d'interface, alors qu'un champ ne peut pas

Remarques:

EventHandler est déclaré comme délégué suivant :

public delegate void EventHandler (object sender, EventArgs e)

il prend un expéditeur (de type Objet) 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 EventHandler plutôt.

Référer ici pour la documentation sur EventHandler

Voici un autre bon lien auquel se référer.http://csharpindegree.com/Articles/Chapter2/Events.aspx

En bref, ce qu'il faut retenir de l'article : les événements sont encapsulés sur les délégués.

Citation de l'article :

Supposons que les événements n’existent pas en tant que concept en C#/.NET.Comment une autre classe s’abonnerait-elle à un événement ?Trois options :

  1. Une variable déléguée publique

  2. Une variable déléguée adossée à une propriété

  3. 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 abhorrons les variables publiques.

L'option 2 est légèrement meilleure, mais permet aux abonnés de se remplacer efficacement les uns les autres - 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 correspond essentiellement à ce que les événements vous offrent, mais avec une convention garantie (générée par le compilateur et soutenue par des indicateurs supplémentaires dans l'IL) et une implémentation "gratuite" si vous êtes satisfait de la sémantique que vous offrent les événements de type champ.L'abonnement et le désabonnement aux événements sont encapsulés sans permettre un accès arbitraire à la liste des gestionnaires d'événements, et les langages peuvent simplifier les choses en fournissant une syntaxe pour la déclaration et l'abonnement.

NOTE:Si vous avez accès à C# 5.0 libéré, lisez les « Limitations relatives à 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 d’avoir un exemple simple et concret.En voici donc un pour la communauté.Tout d'abord, je montre comment vous pouvez utiliser des 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 un article par John Skeet.

Exemple 1:Utiliser 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ù Person 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.Lorsqu'une personne sélectionne une personne dans la liste déroulante, les étiquettes du contrôle utilisateur sont mises à jour pour afficher les propriétés de la personne sélectionnée.

enter image description here

Voici comment cela fonctionne.Nous disposons de trois fichiers qui nous aident à rassembler cela :

  • Mediator.cs – la classe statique contient les délégués
  • Form1.cs -- formulaire principal
  • DetailView.cs - le contrôle utilisateur affiche tous les détails

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 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`.
}

D'accord.C'est comme ça que ça fonctionnerait sans utiliser d'événements et en utilisant simplement des délégués.Nous mettons simplement un délégué public dans une classe - vous pouvez le rendre statique ou singleton, ou autre.Super.

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 très nombreuses raisons.Quelles sont nos options?Comme le décrit John Skeet, voici nos options :

  1. Une variable déléguée publique (c'est ce que nous venons de faire ci-dessus.ne fais pas ça.je viens de te dire ci-dessus pourquoi c'est mauvais)
  2. Placez le délégué dans une propriété avec un get/set (le problème ici est que les abonnés pourraient se remplacer les uns les autres - nous pourrions donc souscrire un tas de méthodes au délégué et ensuite nous pourrions accidentellement dire PersonChangedDel = null, effaçant tous les autres abonnements.L'autre problème qui reste ici est que puisque les utilisateurs ont accès au délégué, ils peuvent appeler les cibles dans la liste d'appel - nous ne voulons pas que les utilisateurs externes aient accès au moment où déclencher nos événements.
  3. Une variable déléguée avec les méthodes AddXXXHandler et RemoveXXXHandler

Cette troisième option est essentiellement ce que nous offre un événement.Lorsque nous déclarons un EventHandler, il nous donne accès à un délégué -- pas publiquement, pas en tant que propriété, mais comme ceci, 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 appuyez sur F12 sur EventHandler, cela vous montrera que la définition est juste un délégué générique avec l'objet "expéditeur" supplémentaire :

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 une seule propriété :

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

J'espère que cela vous montre un peu pourquoi nous organisons des événements et en quoi ils sont différents - mais fonctionnellement identiques - en tant que délégués.

Vous pouvez également utiliser des événements dans les déclarations d'interface, ce qui n'est pas le cas pour les délégués.

Quelle grande incompréhension 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), 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 également un type.Or, dans le cas d'un événement, le type d'événement doit être précisé 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 ce qui suit Observation: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 elle est décrite dans la section II.18 Définir des événements de ECMA-335 (CLI) Partitions I à VI:

En utilisation typique, le TypeSpec (si 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 de délégué de support.En vérité, 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 explicitement un événement en C#, vous êtes libre de choisir la manière dont vous stockez le gestionnaires d'événements (noter que gestionnaires d'événements sont des exemples de type d'événement, qui à son tour est obligatoirement un type de 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és) dans une structure de données telle qu'un List ou un Dictionary ou tout autre autre, ou même dans un champ de délégué de soutien.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 deux un type particulier de délégué.C# et vb.net peuvent générer automatiquement du code pour les méthodes d'ajout et de suppression qui définiront un délégué pour détenir les abonnements aux événements, et ajouteront/supprimeront le délégué transmis à/depuis ce délégué d'abonnement.VB.net générera également automatiquement du code (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 même s’il est courant de gérer les abonnements aux événements à l’aide d’un délégué multicast, 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énements dans .net pensait apparemment qu'il devrait y avoir un moyen public de les générer, ni C# ni vb.net n'utilisent cette fonctionnalité.

Pour définir un événement de manière simple :

L'événement est un RÉFÉRENCE à un délégué avec deux restrictions

  1. Ne peut pas être invoqué directement
  2. Ne peut pas recevoir de valeurs directement (par exemple, eventObj = déléguéMethod)

Au-dessus de deux se trouvent les points faibles des délégués et ils sont abordés lors de l'événement.Un exemple de code complet pour montrer la différence entre Fiddler est ici https://dotnetfiddle.net/5iR3fB .

Basculez le commentaire entre Événement et Délégué et le code client qui appelle/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();
    }


}

Covariance et Contravariance offrent une flexibilité supplémentaire aux objets délégués.En revanche, un événement n’a pas de tels concepts.

  • Covariance Vous permet d'attribuer une méthode au délégué où le type de retour de la méthode est une classe dérivée de la classe qui spécifie le type de retour du délégué.
  • Contravariance Vous permet d'attribuer une méthode au délégué où le type de paramètre de la méthode est une classe de base de la classe spécifiée comme paramètre du délégué.

Delegate est un pointeur de fonction de type sécurisé.L'événement est une implémentation du modèle de conception éditeur-abonné utilisant un délégué.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top