Quelles sont les différences entre les délégués et les événements?


317

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?



2
cela explique avec exemple un coup d'oeil unitygeek.com/delegates-events-unity
Rahul Lalit

Réponses:


283

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.


44
Bien entendu, 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.
Jeppe Stig Nielsen

7
Pas tout à fait vrai. Vous pouvez déclarer un événement sans instance de délégué principal. En c #, vous pouvez implémenter un événement de manière explicite et utiliser une structure de données backend différente de votre choix.
Miguel Gamboa

3
@mmcdole pouvez-vous fournir un exemple pour expliquer son?
vivek nuna

103

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 Animalclasse peut augmenter RaiseEventsimplement en l'appelant animal.RaiseEvent().

Pour éviter ces points faibles, vous pouvez utiliser eventsen 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:

  1. Vous n'utilisez pas une propriété publique mais un champ public (en utilisant des événements, le compilateur protège vos champs contre les 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 organiser 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é 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 EventHandlerplace.

Reportez-vous ici pour la documentation sur EventHandler


7
Tout avait l'air génial jusqu'à ce que je tombe sur "Personne en dehors de votre classe ne peut organiser l'événement." Qu'est-ce que ça veut dire? Personne ne peut-il appeler RaiseEventtant qu'une méthode d'appel a accès à une instance de animaldans le code qui utilise l'événement?
dance2die

11
@Sung Events ne peut être ressuscité que depuis l'intérieur de la classe, je n'ai peut-être pas été clair à expliquer cela. Avec les événements, vous pouvez appeler la fonction qui déclenche l'événement (encapsulation), mais elle ne peut être générée qu'à partir de la classe qui le définit. Faites-moi savoir si je ne suis pas clair.
faby

1
"Les événements ne peuvent pas être directement attribués." À moins que je ne vous comprenne mal, ce n'est pas vrai. Voici un exemple: gist.github.com/Chiel92/36bb3a2d2ac7dd511b96
Chiel ten Brinke

2
@faby, tu veux dire que même si l'événement est déclaré public, je ne peux toujours pas le faire animal.Run(this, new ArgsSpecial("Run faster");?
Pap

1
@ChieltenBrinke Bien sûr, l'événement peut être attribué aux membres de la classe ... mais pas autrement.
Jim Balter

94

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).


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

1
Pourrions-nous donc dire qu'un événement est un type "spécial" de délégué?
Pap

Je ne comprends pas votre point. Vous pouvez utiliser un délégué pour «alerter quelqu'un quand quelque chose se passe». Peut-être que vous ne feriez pas cela, mais vous pouvez et donc ce n'est pas une propriété inhérente à l'événement.
steve

@Jorge Córdoba exemple de délégué et délégué d'événements est propriétaire d'un journal et d'événements (abonnez-vous ou désabonnez-vous) et certaines personnes achètent le journal et certaines personnes n'achètent pas le journal signifie que le propriétaire du journal ne peut pas forcer chaque personne à acheter le journal mon point vrai ou faux?
Rahul_Patil

37

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:

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

  2. Une variable déléguée soutenue par 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 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.


Explication agréable et concise. Thanx
Pap

C'est plus une préoccupation théorique qu'autre chose, mais FWIW J'ai toujours pensé que l'argument "Option 1 est mauvais parce que nous n'aimons pas les variables publiques" pourrait utiliser un peu plus de clarification. S'il dit cela parce que c'est une "mauvaise pratique de POO", techniquement une public Delegatevariable 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.
2018

Bien que j'aimerais également donner des conseils plus pratiques, si vous êtes dans une situation où vous souhaitez vous assurer qu'il n'y a qu'un seul gestionnaire, créer vos propres AddXXXHandlerméthodes avec une private Delegatevariable 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 Delegatepour pouvoir effacer tous les gestionnaires ( eventne vous donne aucun moyen de le faire).
jrh

7

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.

entrez la description de l'image ici

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

  • Mediator.cs - la classe statique détient 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 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:

  1. Une variable de délégué public (c'est ce que nous venons de faire ci-dessus. Ne faites pas cela. Je viens de vous dire ci-dessus pourquoi c'est mauvais)
  2. Mettez 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 nous pourrions alors accidentellement dire 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.
  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, 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.


Bien que j'apprécie tout le bon travail de cet article et que j'aimais en lire la plupart, je pense toujours qu'un problème n'est pas résolu - 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 OnPersonChangechaque fois que vous avez une référence au singleton. Vous devriez peut-être mentionner que l' Mediatorapproche n'empêche pas ce comportement particulier et est plus proche d'un bus d'événements.
Ivaylo Slavov

6

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


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

1
Que veux-tu dire exactement? Vous pouvez avoir à l' Action a { get; set; }intérieur d'une définition d'interface.
Chiel ten Brinke

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 interfacefait), 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 Listou a Dictionaryou 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é.


4

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é.


3

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

  1. Ne peut être invoqué directement
  2. Les valeurs ne peuvent pas être attribuées directement (par exemple, eventObj = delegateMethod)

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


}

2

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é.


0

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.