Différence entre les événements et les délégués et ses applications respectives [fermé]

107

Je ne vois pas d'avantages à utiliser des événements par rapport aux délégués, à part être du sucre syntaxique. Peut-être que je ne comprends pas, mais il semble que l'événement n'est qu'un espace réservé pour le délégué.

Pourriez-vous m'expliquer les différences et quand les utiliser? Quels sont les avantages et les inconvénients? Notre code est fortement ancré dans les événements, et je veux aller au fond des choses.

Quand utiliseriez-vous des délégués lors d'événements et vice versa? Veuillez indiquer votre expérience du monde réel avec les deux, par exemple dans le code de production.

licol
la source
Ouais, comprendre les différences était vraiment difficile, elles se ressemblent et semblent faire la même chose au premier regard
Robert Gould
1
Voir aussi cette question .
Dimitri C.
1
La différence entre deux événements et délégués est une question de fait, pas d'opinion. La question demande des applications respectives car elles illustrent la différence des problèmes que les technologies résolvent. Ce n'est pas non plus une question d'opinion car personne n'a demandé ce qui était le mieux. Aucune partie de cette question n'est une question d'opinion, et cette déclaration n'est pas non plus une opinion. À mon avis. Avez-vous votre badge?
Peter Wone

Réponses:

49

Du point de vue technique, d'autres réponses ont abordé les différences.

D'un point de vue sémantique, les événements sont des actions déclenchées par un objet lorsque certaines conditions sont remplies. Par exemple, ma classe Stock a une propriété appelée Limite et déclenche un événement lorsque le cours des actions atteint la limite. Cette notification se fait via un événement. Que quelqu'un se soucie réellement de cet événement et y adhère est au-delà de la préoccupation de la classe des propriétaires.

Un délégué est un terme plus générique pour décrire une construction similaire à un pointeur en termes C / C ++. Tous les délégués dans .Net sont des délégués multicast. D'un point de vue sémantique, ils sont généralement utilisés comme une sorte d'entrée. En particulier, ils constituent un moyen idéal de mettre en œuvre le modèle de stratégie . Par exemple, si je souhaite trier une liste d'objets, je peux fournir une stratégie de comparaison à la méthode pour indiquer à l'implémentation comment comparer deux objets.

J'ai utilisé les deux méthodes dans le code de production. Des tonnes de mes objets de données avertissent lorsque certaines propriétés sont remplies. Exemple le plus basique, chaque fois qu'une propriété change, un événement PropertyChanged est déclenché (voir interface INotifyPropertyChanged). J'ai utilisé des délégués dans le code pour fournir différentes stratégies de transformation de certains objets en chaîne. Cet exemple particulier était une liste ToString () glorifiée d'implémentations pour un type d'objet particulier pour l'afficher aux utilisateurs.

Szymon Rozga
la source
4
Il me manque peut-être quelque chose, mais un gestionnaire d'événements n'est-il pas un type de délégué?
Powerlord
1
Ma réponse répond aux questions Edit # 1 et # 2; différences du point de vue de l’utilisation. Pour les besoins de cette discussion, ils sont différents, même si, d'un point de vue technique, vous avez raison. Jetez un œil aux autres réponses pour les différences techniques.
Szymon Rozga
3
"Tous les délégués dans .Net sont des délégués multicast"? Même les délégués qui renvoient des valeurs?
Qwertie
5
Oui. Pour l'historique, jetez un œil à msdn.microsoft.com/en-us/magazine/cc301816.aspx . Consultez: msdn.microsoft.com/en-us/library/system.delegate.aspx . S'ils renvoient des valeurs, la valeur renvoyée est l'évaluation du dernier délégué de la chaîne.
Szymon Rozga
les délégués sont des types de référence qui pointent vers les gestionnaires d'événements définis dans la classe d'abonné. En d'autres termes, le délégué est utilisé comme lien entre l'événement (dans l'éditeur) et le gestionnaire d'événements défini dans l'abonné. Dans une application, plusieurs abonnés devront écouter un événement, et dans de tels scénarios, les délégués nous offrent un moyen efficace de relier l'éditeur et les abonnés.
josepainumkal
55

Le mot clé eventest un modificateur de portée pour les délégués multicast. Les différences pratiques entre cela et la simple déclaration d'un délégué de multidiffusion sont les suivantes:

  • Vous pouvez utiliser eventdans une interface.
  • L'accès d'appel au délégué de multidiffusion est limité à la classe déclarante. Le comportement est comme si le délégué était privé pour l'appel. Aux fins de l'affectation, l'accès est tel que spécifié par un modificateur d'accès explicite (par exemple public event).

Par intérêt, vous pouvez appliquer +et -à des délégués de multidiffusion, et c'est la base de la syntaxe +=et -=pour l'affectation de combinaison de délégués à des événements. Ces trois extraits sont équivalents:

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B + C;

Exemple deux, illustrant à la fois l'assignation directe et l'assignation combinée.

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B;
A += C;

Exemple trois: syntaxe plus familière. Vous connaissez probablement l'attribution de null pour supprimer tous les gestionnaires.

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = null;
A += B;
A += C;

Comme les propriétés, les événements ont une syntaxe complète que personne n'utilise jamais. Ce:

class myExample 
{
  internal EventHandler eh;

  public event EventHandler OnSubmit 
  { 
    add 
    {
      eh = Delegate.Combine(eh, value) as EventHandler;
    }
    remove
    {
      eh = Delegate.Remove(eh, value) as EventHandler;
    }
  }

  ...
}

... fait exactement la même chose:

class myExample 
{
  public event EventHandler OnSubmit;
}

Les méthodes d'ajout et de suppression sont plus visibles dans la syntaxe plutôt stilted que VB.NET utilise (pas de surcharge d'opérateur).

Peter Wone
la source
6
+ pour "L'accès d'invocation au délégué de multidiffusion est limité à la classe déclarante" - c'est pour moi le point de différence clé entre les délégués et les événements.
RichardOD
2
Une autre différence importante (mentionnée par itowlson ci-dessous) est que l'on ne peut pas désabonner tous les gestionnaires d'événements en les affectant à un événement, mais ils pourraient le faire avec un délégué. (Au fait, la vôtre était la réponse la plus utile de toutes ces réponses).
Roman Starkov
4
Aussi pratique que puissent être Google et stackoverflow, tout cela et bien plus encore est disponible dans des détails stupéfiants dans la spécification du langage C #, disponible gratuitement auprès de Microsoft. Je sais qu'à première vue, Dieu a créé le manuel et Jon Skeet l'a avalé, mais il y a d'autres copies :)
Peter Wone
12

Les événements sont du sucre syntaxique. Ils sont délicieux. Quand je vois un événement, je sais quoi faire. Quand je vois un délégué, je ne suis pas si sûr.

La combinaison d'événements avec des interfaces (plus de sucre) en fait une collation alléchante. Les délégués et les classes abstraites virtuelles pures sont beaucoup moins appétissants.

Sean
la source
c'est comme ça que je le vois aussi. Je veux une explication plus profonde et plus douce :)
13
Cependant, trop de sucre fait une graisse ... = P
Erik Forbes
5

Les événements sont marqués comme tels dans les métadonnées. Cela permet à des éléments tels que les concepteurs Windows Forms ou ASP.NET de distinguer les événements de simples propriétés de type délégué et de leur fournir une prise en charge appropriée (en les affichant spécifiquement dans l'onglet Événements de la fenêtre Propriétés).

Une autre différence par rapport à une propriété de type délégué est que les utilisateurs peuvent uniquement ajouter et supprimer des gestionnaires d'événements, alors qu'avec une propriété de type délégué, ils peuvent définir la valeur:

someObj.SomeCallback = MyCallback;  // okay, replaces any existing callback
someObj.SomeEvent = MyHandler;  // not okay, must use += instead

Cela aide à isoler les abonnés à l'événement: je peux ajouter mon gestionnaire à un événement, et vous pouvez ajouter votre gestionnaire au même événement, et vous n'écraserez pas accidentellement mon gestionnaire.

Itowlson
la source
4

Bien que les événements soient généralement mis en œuvre avec des délégués de multidiffusion, il n'est pas nécessaire qu'ils soient utilisés de cette manière. Si une classe expose un événement, cela signifie que la classe expose deux méthodes. Leurs significations sont, en substance:

  1. Voici un délégué. Veuillez l'invoquer lorsque quelque chose d'intéressant se produit.
  2. Voici un délégué. Vous devez détruire toute référence à celui-ci dès que possible (et ne plus l'appeler).

Le moyen le plus courant pour une classe de gérer un événement qu'elle expose est de définir un délégué de multidiffusion et d'ajouter / supprimer tous les délégués qui sont passés aux méthodes ci-dessus, mais il n'est pas nécessaire qu'elles fonctionnent de cette façon. Malheureusement, l'architecture d'événement ne parvient pas à faire certaines choses qui auraient rendu les approches alternatives beaucoup plus propres (par exemple, demander à la méthode d'abonnement de renvoyer un MethodInvoker, qui serait conservé par l'abonné; pour désabonner un événement, appelez simplement la méthode retournée). sont de loin l'approche la plus courante.

supercat
la source
4

pour comprendre les différences, vous pouvez regarder ces 2 exemples

Exemple avec des délégués (Action dans ce cas qui est une sorte de délégué qui ne retourne 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 ça

Animale 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'avais 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 se lever RaiseEventsimplement en l'appelant animal.RaiseEvent().

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

Votre classe d'animaux changera de cette façon

public class ArgsSpecial :EventArgs
   {
        public ArgsSpecial (string val)
        {
            Operation=val;
        }

        public string Operation {get; set;}
   } 



 public class Animal
    {
       public event EventHandler<ArgsSpecial> Run = delegate{} //empty delegate. In this way you are sure that value is always != null because no one outside of the class can change it

       public void RaiseEvent()
       {  
          Run(this, new ArgsSpecial("Run faster"));
       }
    }

pour appeler des événements

 Animale 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 (avec 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, vous ne pouvez pas faire 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é comme le 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.

Vous pouvez également utiliser à la EventHAndlerplace cet exemple qui utiliseEventHandler<ArgsSpecial>

référez-vous ici pour la documentation sur EventHandler

faby
la source
3

Edit # 1 Quand utiliseriez-vous des délégués sur des événements et par rapport à un autre? Veuillez indiquer votre expérience du monde réel avec les deux, par exemple dans le code de production.

Lorsque je conçois mes propres API, je définis des délégués qui sont passés en paramètres aux méthodes ou aux constructeurs de classes:

  • Pour qu'une méthode puisse implémenter un modèle de 'méthode modèle' simple (par exemple, les délégués Predicateet Actionsont passés aux classes de collection génériques .Net)
  • Ou pour que la classe puisse faire un 'callback' (typiquement un callback vers une méthode de la classe qui l'a créée).

Ces délégués sont généralement non optionnels au moment de l'exécution (c'est - à-dire qu'ils ne doivent pas l'être null).

J'ai tendance à ne pas utiliser d'événements; mais là où j'utilise des événements, je les utilise pour signaler éventuellement des événements à zéro, un ou plusieurs clients qui pourraient être intéressés, c'est-à-dire quand il est logique qu'une classe (par exemple la System.Windows.Formclasse) existe et s'exécute, qu'un client ait ou non a ajouté un gestionnaire d'événements à son événement (par exemple, l'événement «mouse down» du formulaire existe, mais il est facultatif si un client externe est intéressé par l'installation d'un gestionnaire d'événements sur cet événement).

ChrisW
la source
2

Bien que je n'ai aucune raison technique à cela, j'utilise des événements dans le code de style UI, en d'autres termes, dans les niveaux supérieurs du code, et j'utilise des délégués pour une logique qui approfondit le code. Comme je l'ai dit, vous pouvez utiliser l'un ou l'autre, mais je trouve que ce modèle d'utilisation est logique, si rien d'autre, il aide à documenter les types de rappels et leur hiérarchie également.


Edit: Je pense que la différence dans les modèles d'utilisation que j'ai serait que, je trouve parfaitement acceptable d'ignorer les événements, ce sont des hooks / stubs, si vous avez besoin de connaître l'événement, écoutez-les, si vous ne vous souciez pas de l'événement ignore tout simplement. C'est pourquoi je les utilise pour l'interface utilisateur, un style d'événement Javascript / Browser. Cependant, lorsque j'ai un délégué, je m'attends VRAIMENT à ce que quelqu'un gère la tâche du délégué et lance une exception s'il n'est pas géré.

Robert Gould
la source
Souhaitez-vous développer cela car j'utilise également des evens dans l'interface utilisateur? Un bon exemple suffirait .... merci
1

La différence entre les événements et les délégués est beaucoup plus petite que je ne le pensais. Je viens de publier une vidéo YouTube super courte sur le sujet: https://www.youtube.com/watch?v=el-kKK-7SBU

J'espère que cela t'aides!

Pontus Wittenmark
la source
2
Bienvenue dans Stack Overflow! Bien que cela puisse théoriquement répondre à la question, il serait préférable d'inclure ici les parties essentielles de la réponse et de fournir le lien pour référence.
GhostCat
1

Si nous n'utilisons que délégué à la place de l'événement, l'abonné a la possibilité de cloner (), invoquer () le délégué lui-même comme indiqué ci-dessous dans l'image. Ce qui n'est pas juste.

entrez la description de l'image ici

C'est la principale différence entre l'événement et le délégué. l'abonné n'a qu'un seul droit c'est à dire écouter les événements

La classe ConsoleLog abonne des événements de journal via EventLogHandler

public class ConsoleLog
{
    public ConsoleLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write on console : " + str);
    }
}

La classe FileLog abonne des événements de journal via EventLogHandler

public class FileLog
{
    public FileLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write in File : " + str);
    }
}

La classe d'opération publie des événements de journal

public delegate void logDelegate(string str);
public class Operation
{
    public event logDelegate EventLogHandler;
    public Operation()
    {
        new FileLog(this);
        new ConsoleLog(this);
    }

    public void DoWork()
    {
        EventLogHandler.Invoke("somthing is working");
    }
}
Narottam Goyal
la source