Délégué vs Interfaces - D'autres clarifications sont-elles disponibles?

23

Après avoir lu l'article - Quand utiliser des délégués au lieu d'interfaces (Guide de programmation C #) , j'ai besoin d'aide pour comprendre les points ci-dessous, que j'ai trouvé pas si clairs (pour moi). Avez-vous des exemples ou des explications détaillées à leur sujet?

Utilisez un délégué lorsque:

  • Un modèle de conception de concours complet est utilisé.
  • Il est souhaitable d'encapsuler une méthode statique.
  • Une composition facile est souhaitée.
  • Une classe peut nécessiter plusieurs implémentations de la méthode.

Utilisez une interface lorsque:

  • Il existe un groupe de méthodes connexes qui peuvent être appelées.
  • Une classe n'a besoin que d'une implémentation de la méthode.

Mes questions sont,

  1. Que signifient-ils par un modèle de design complet?
  2. Comment la composition s'avère-t-elle facile si un délégué est utilisé?
  3. s'il y a un groupe de méthodes apparentées qui peuvent être appelées, alors utilisez l'interface. Quel est son avantage?
  4. si une classe n'a besoin que d'une implémentation de la méthode, utiliser l'interface-comment est-elle justifiée en termes d'avantages?
WinW
la source

Réponses:

11

Que signifient-ils par un modèle de design complet?

Ils se réfèrent très probablement à une implémentation du modèle d'observateur qui est une construction de langage de base en C #, exposée comme « événements ». L'écoute des événements est possible en y accrochant un délégué. Comme l'a souligné Yam Marcovic,EventHandler s'agit du type de délégué de base conventionnel pour les événements, mais tout type de délégué peut être utilisé.

Comment la composition s'avère-t-elle facile si un délégué est utilisé?

Cela fait probablement référence à la flexibilité offerte par les délégués. Vous pouvez facilement «composer» certains comportements. Avec l'aide de lambdas , la syntaxe pour ce faire est également très concise. Prenons l'exemple suivant.

class Bunny
{
    Func<bool> _canHop;

    public Bunny( Func<bool> canHop )
    {
        _canHop = canHop;
    }

    public void Hop()
    {
        if ( _canHop() )  Console.WriteLine( "Hop!" );
    }
}

Bunny captiveBunny = new Bunny( () => IsBunnyReleased );
Bunny lazyBunny = new Bunny( () => !IsLazyDay );
Bunny captiveLazyBunny = new Bunny( () => IsBunnyReleased && !IsLazyDay );

Faire quelque chose de similaire avec les interfaces vous obligerait à utiliser le modèle de stratégie ou à utiliser une base (abstraite)Bunny classe de partir de laquelle vous étendez des lapins plus spécifiques.

s'il y a un groupe de méthodes apparentées qui peuvent être appelées, alors utilisez l'interface - Quel est son avantage?

Encore une fois, je vais utiliser des lapins pour montrer comment ce serait plus facile.

interface IAnimal
{
    void Jump();
    void Eat();
    void Poo();
}

class Bunny : IAnimal { ... }
class Chick : IAnimal { ... }

// Using the interface.
IAnimal bunny = new Bunny();
bunny.Jump();  bunny.Eat();  bunny.Poo();
IAnimal chick = new Chick();
chick.Jump();  chick.Eat();  chick.Poo();

// Without the interface.
Action bunnyJump = () => bunny.Jump();
Action bunnyEat = () => bunny.Eat();
Action bunnyPoo = () => bunny.Poo();
bunnyJump(); bunnyEat(); bunnyPoo();
Action chickJump = () => chick.Jump();
Action chickEat = () => chick.Eat();
...

si une classe n'a besoin que d'une implémentation de la méthode, utiliser l'interface-comment est-elle justifiée en termes d'avantages?

Pour cela, considérons à nouveau le premier exemple avec le lapin. Si une seule implémentation est nécessaire - aucune composition personnalisée n'est jamais requise -, vous pouvez exposer ce comportement en tant qu'interface. Vous n'aurez jamais à construire les lambdas, vous pouvez simplement utiliser l'interface.

Conclusion

Les délégués offrent beaucoup plus de flexibilité, tandis que les interfaces vous aident à établir des contrats solides. Par conséquent, je trouve le dernier point mentionné, "Une classe peut nécessiter plusieurs implémentations de la méthode." , de loin le plus pertinent.

Une autre raison d'utiliser des délégués est lorsque vous souhaitez exposer uniquement une partie d'une classe dont vous ne pouvez pas ajuster le fichier source.

Comme exemple d'un tel scénario (flexibilité maximale, pas besoin de modifier les sources), considérez cette implémentation de l'algorithme de recherche binaire pour toute collection possible en passant simplement deux délégués.

Steven Jeuris
la source
12

Un délégué est quelque chose comme une interface pour une signature de méthode unique, qui n'a pas besoin d'être implémenté explicitement comme une interface régulière. Vous pouvez le construire sur le pouce.

Une interface n'est qu'une structure de langage représentant un contrat - "Je garantis par la présente que je mets à disposition les méthodes et propriétés suivantes".

De plus, je ne suis pas tout à fait d'accord pour dire que les délégués sont principalement utiles lorsqu'ils sont utilisés comme solution au modèle observateur / abonné. C'est, cependant, une solution élégante au "problème de verbosité java".

Pour vos questions:

1 & 2)

Si vous souhaitez créer un système d'événements en Java, vous utilisez généralement une interface pour propager l'événement, quelque chose comme:

interface KeyboardListener
{
    void KeyDown(int key);
    void KeyUp(int key)
    void KeyPress(int key);
    .... and so on
}

Cela signifie que votre classe devra implémenter explicitement toutes ces méthodes et fournir des stubs pour toutes, même si vous voulez simplement implémenter KeyPress(int key) .

En C #, ces événements seraient représentés comme des listes de délégués, masqués par le mot-clé "event" en c #, un pour chaque événement singulier. Cela signifie que vous pouvez facilement vous abonner à ce que vous voulez sans surcharger votre classe avec des méthodes "clés" publiques, etc.

+1 pour les points 3-5.

Aditionellement:

Les délégués sont très utiles lorsque vous voulez fournir par exemple une fonction "map", qui prend une liste et projette chaque élément dans une nouvelle liste, avec la même quantité d'éléments, mais différents d'une manière ou d'une autre. Essentiellement, IEnumerable.Select (...).

IEnumerable.Select prend un Func<TSource, TDest> , qui est un délégué encapsulant une fonction qui prend un élément TSource et transforme cet élément en un élément TDest.

En Java, cela devrait être implémenté à l'aide d'une interface. Il n'y a souvent pas de place naturelle pour implémenter une telle interface. Si une classe contient une liste qu'elle veut transformer d'une manière ou d'une autre, il n'est pas très naturel qu'elle implémente l'interface "ListTransformer", d'autant plus qu'il pourrait y avoir deux listes différentes qui devraient être transformées de différentes manières.

Bien sûr, vous pouvez utiliser des classes anonymes qui sont un concept similaire (en java).

Max
la source
Pensez à modifier votre message pour répondre à ses questions
Yam Marcovic
@YamMarcovic Vous avez raison, j'ai déambulé un peu en forme libre au lieu de répondre directement à ses questions. Pourtant, je pense que cela explique un peu les mécanismes impliqués. De plus, ses questions étaient un peu moins bien formulées lorsque j'ai répondu à la question. : p
Max
Naturellement. Ça m'arrive aussi, et ça a sa valeur en soi. C'est pourquoi je l'ai seulement suggéré , mais je ne m'en suis pas plaint. :)
Yam Marcovic
3

1) Modèles d'événementiel, le classique est le modèle Observer, voici un bon lien Microsoft Microsoft Talk Observer

L'article auquel vous avez lié n'est pas très bien écrit et rend les choses plus compliquées que nécessaire. L'activité statique est du bon sens, vous ne pouvez pas définir une interface avec des membres statiques, donc si vous voulez un comportement polymorphe sur une méthode statique, vous utiliseriez un délégué.

Le lien ci-dessus parle des délégués et pourquoi ils sont bons pour la composition, donc cela pourrait aider votre requête.

Ian
la source
3

Tout d'abord, bonne question. J'admire votre concentration sur l'utilité plutôt que d'accepter aveuglément les «meilleures pratiques». +1 pour cela.

J'ai déjà lu ce guide. Vous devez vous souvenir de quelque chose à ce sujet - c'est juste un guide, principalement pour les nouveaux arrivants C # qui savent programmer mais ne sont pas si familiers avec la façon de faire C #. Ce n'est pas tant une page de règles qu'une page qui décrit comment les choses sont déjà faites habituellement. Et comme ils sont déjà effectués de cette façon partout, ce pourrait être une bonne idée de rester cohérent.

J'arrive au fait, répondant à vos questions.

Tout d'abord, je suppose que vous savez déjà ce qu'est une interface. Quant à un délégué, il suffit de dire que c'est une structure contenant un pointeur typé vers une méthode, ainsi qu'un pointeur optionnel vers l'objet représentant l' thisargument de cette méthode. Dans le cas de méthodes statiques, ce dernier pointeur est nul.
Il existe également des délégués de multidiffusion, qui sont exactement comme des délégués, mais plusieurs de ces structures peuvent leur être affectées (ce qui signifie qu'un seul appel à Invoke sur un délégué de multidiffusion appelle toutes les méthodes de la liste d'invocation qui lui est affectée).

Que signifient-ils par un modèle de design complet?

Ils signifient l'utilisation d'événements en C # (qui a des mots clés spéciaux pour implémenter de manière sophistiquée ce modèle extrêmement utile). Les événements en C # sont alimentés par des délégués de multidiffusion.

Lorsque vous définissez un événement, comme dans cet exemple:

class MyClass {
  // Note: EventHandler is just a multicast delegate,
  // that returns void and accepts (object sender, EventArgs e)!
  public event EventHandler MyEvent;

  public void DoSomethingThatTriggersMyEvent() {
    // ... some code
    var handler = MyEvent;
    if (handler != null)
      handler(this, EventArgs.Empty);
    // ... some other code
  }
}

Le compilateur transforme cela en fait dans le code suivant:

class MyClass {
  private EventHandler MyEvent = null;

  public void add_MyEvent(EventHandler value) {
    MyEvent += value;
  }

  public void remove_MyEvent(EventHandler value) {
    MyEvent -= value;
  }

  public void DoSomethingThatTriggersMyEvent() {
    // ... some code
    var handler = MyEvent;
    if (handler != null)
      handler(this, EventArgs.Empty);
    // ... some other code
  }
}

Vous vous abonnez ensuite à un événement en faisant

MyClass instance = new MyClass();
instance.MyEvent += SomeMethodInMyClass;

Qui se compile en

MyClass instance = new MyClass();
instance.add_MyEvent(new EventHandler(SomeMethodInMyClass));

C'est donc un événement en C # (ou .NET en général).

Comment la composition s'avère-t-elle facile si un délégué est utilisé?

Cela peut facilement être démontré:

Supposons que vous ayez une classe qui dépend d'un ensemble d'actions à lui passer. Vous pouvez encapsuler ces actions dans une interface:

interface RequiredMethods {
  void DoX();
  int DoY();
};

Et quiconque souhaitant passer des actions à votre classe devra d'abord implémenter cette interface. Ou vous pourriez leur faciliter la vie en fonction de la classe suivante:

sealed class RequiredMethods {
  public Action DoX;
  public Func<int> DoY();
}

De cette façon, les appelants n'ont qu'à créer une instance de RequiredMethods et à lier des méthodes aux délégués lors de l'exécution. C'est généralement plus facile.

Cette façon de faire est extrêmement bénéfique dans les bonnes circonstances. Pensez-y - pourquoi dépendre d'une interface alors que tout ce qui vous importe vraiment est de vous faire passer une implémentation?

Avantages de l'utilisation d'interfaces lorsqu'il existe un groupe de méthodes connexes

Il est avantageux d'utiliser des interfaces car les interfaces nécessitent normalement des implémentations explicites au moment de la compilation. Cela signifie que vous créez une nouvelle classe.
Et si vous avez un groupe de méthodes connexes dans un seul package, il est avantageux que ce package soit réutilisable par d'autres parties du code. Donc, s'ils peuvent simplement instancier une classe au lieu de construire un ensemble de délégués, c'est plus facile.

Avantages de l'utilisation d'interfaces si une classe n'a besoin que d'une implémentation

Comme indiqué précédemment, les interfaces sont implémentées au moment de la compilation - ce qui signifie qu'elles sont plus efficaces que d'appeler un délégué (qui est un niveau d'indirection en soi).

"Une implémentation" peut signifier une implémentation qui existe un seul endroit bien défini.
Sinon, une implémentation peut provenir de n'importe où dans le programme qui se trouve juste être conforme à la signature de la méthode. Cela permet plus de flexibilité, car les méthodes n'ont besoin que de se conformer à la signature attendue, plutôt que d'appartenir à une classe qui implémente explicitement une interface spécifique. Mais cette flexibilité peut avoir un coût, et rompt en fait le principe de substitution de Liskov , car la plupart du temps vous voulez une explication, car elle minimise les risques d'accidents. Tout comme la frappe statique.

Le terme peut également faire référence aux délégués de multidiffusion ici. Les méthodes déclarées par les interfaces ne peuvent être implémentées qu'une seule fois dans une classe d'implémentation. Mais les délégués peuvent accumuler plusieurs méthodes, qui seront appelées séquentiellement.

Donc, dans l'ensemble, il semble que le guide ne soit pas suffisamment informatif et fonctionne simplement comme ce qu'il est - un guide, pas un livre de règles. Certains conseils peuvent en fait sembler un peu contradictoires. C'est à vous de décider quand il convient d'appliquer quoi. Le guide semble seulement nous donner un chemin général.

J'espère que vos questions ont été répondues à votre satisfaction. Et encore une fois, bravo pour la question.

Yam Marcovic
la source
2

Si ma mémoire de .NET tient toujours, un délégué est fondamentalement une fonction ptr ou functor. Il ajoute une couche d'indirection à un appel de fonction afin que les fonctions puissent être remplacées sans que le code appelant doive changer. C'est la même chose qu'une interface, sauf qu'une interface regroupe plusieurs fonctions ensemble, et qu'un implémenteur doit les implémenter ensemble.

Un modèle d'événement, d'une manière générale, est celui où quelque chose répond aux événements d'ailleurs (tels que les messages Windows). L'ensemble des événements est généralement ouvert, ils peuvent venir dans n'importe quel ordre et ne sont pas nécessairement liés les uns aux autres. Les délégués fonctionnent bien pour cela, car chaque événement peut appeler une seule fonction sans avoir besoin d'une référence à une série d'objets d'implémentation qui peuvent également contenir de nombreuses fonctions non pertinentes. De plus (c'est là que ma mémoire .NET est vague), je pense que plusieurs délégués peuvent être attachés à un événement.

Compositer, bien que je ne sois pas très familier avec le terme, consiste essentiellement à concevoir un objet pour avoir plusieurs sous-parties, ou des enfants agrégés auxquels le travail est transmis. Les délégués permettent aux enfants d'être mélangés et appariés d'une manière plus ponctuelle où une interface peut être exagérée ou provoquer trop de couplage et la rigidité et la fragilité qui l'accompagne.

L'avantage d'une interface pour les méthodes associées est que les méthodes peuvent partager l'état de l'objet d'implémentation. Les fonctions de délégué ne peuvent pas partager, ni même contenir, si proprement l'état.

Si une classe a besoin d'une seule implémentation, une interface est plus appropriée car dans n'importe quelle classe implémentant la collection entière, une seule implémentation peut être effectuée, et vous obtenez les avantages d'une classe implémentante (état, encapsulation, etc.). Si l'implémentation peut changer en raison de l'état d'exécution, les délégués fonctionnent mieux car ils peuvent être échangés contre d'autres implémentations sans affecter les autres méthodes. Par exemple, s'il y a trois délégués qui ont chacun deux implémentations possibles, vous auriez besoin de huit classes différentes implémentant l'interface à trois méthodes pour prendre en compte toutes les combinaisons possibles d'états.

kylben
la source
1

Le modèle de conception «événementiel» (mieux connu sous le nom de modèle d'observateur) vous permet d'attacher plusieurs méthodes de la même signature au délégué. Vous ne pouvez pas vraiment faire ça avec une interface.

Je ne suis pas du tout convaincu que la composition soit plus facile pour un délégué qu'une interface. C'est une déclaration très étrange. Je me demande s'il veut dire parce que vous pouvez attacher des méthodes anonymes à un délégué.

pdr
la source
1

La plus grande clarification que je puisse offrir:

  1. Un délégué définit une signature de fonction - quels paramètres une fonction correspondante acceptera et ce qu'elle renverra.
  2. Une interface traite un ensemble complet de fonctions, d'événements, de propriétés et de champs.

Alors:

  1. Lorsque vous souhaitez généraliser certaines fonctions avec la même signature - utilisez un délégué.
  2. Lorsque vous souhaitez généraliser le comportement ou la qualité d'une classe - utilisez une interface.
John Fisher
la source
1

Votre question sur les événements a déjà été bien couverte. Et il est également vrai qu'une interface peut définir plusieurs méthodes (mais en fait pas besoin), alors qu'un type de fonction ne fait que mettre des contraintes sur une fonction individuelle.

La vraie différence est cependant:

  • les valeurs de fonction sont mises en correspondance avec les types de fonction par sous-typage structurel (c'est-à-dire compatibilité implicite avec la structure demandée). Cela signifie que si une valeur de fonction a une signature compatible avec le type de fonction, c'est une valeur valide pour le type.
  • les instances sont mises en correspondance avec les interfaces par sous-typage nominal (c'est-à-dire utilisation explicite d'un nom). Cela signifie que si une instance est membre d'une classe qui implémente explicitement l'interface donnée, l'instance est une valeur valide pour l'interface. Cependant, si l'objet n'a que tous les membres demandés par les interfaces et que tous les membres ont des signatures compatibles, mais n'implémente pas explicitement l'interface, ce n'est pas une valeur valide pour l'interface.

Bien sûr, mais qu'est-ce que cela signifie?

Prenons cet exemple (le code est dans haXe car mon C # n'est pas très bon):

class Collection<T> {
    /* a lot of code we don't care about now */
    public function filter(predicate:T->Bool):Collection<T> { 
         //build a new collection with all elements e, such that predicate(e) == true
    }
    public function remove(e:T):Bool {
         //removes an element from this collection, if contained and returns true, false otherwise
    }
}

Maintenant, la méthode de filtrage permet de passer facilement dans un petit morceau de logique, qui ne connaît pas l'organisation interne de la collection, tandis que la collection ne dépend pas de la logique qui lui est donnée. Génial. Sauf pour un problème:
La collection ne dépend de la logique. La collection suppose de manière inhérente que la fonction transmise est conçue pour tester une valeur par rapport à une condition et renvoyer le succès du test. Notez que toutes les fonctions, qui prennent une valeur en argument et renvoient un booléen ne sont en fait que de simples prédicats. Par exemple, la méthode remove de notre collection est une telle fonction.
Supposons que nous ayons appelé c.filter(c.remove). Le résultat serait une collection avec tous les éléments dec toutclui-même devient vide. C'est regrettable, car naturellement nous nous attendrions cà ce qu'il soit invariant.

L'exemple est très construit. Mais le problème clé est que le code appelant c.filteravec une valeur de fonction comme argument n'a aucun moyen de savoir si cet argument est approprié (c'est-à-dire qu'il conservera finalement l'invariant). Le code qui a créé la valeur de la fonction peut ou non savoir qu'il sera interprété comme un prédicat.

Maintenant, changeons les choses:

interface Predicate<T> {
    function test(value:T):Bool;
}
class Collection<T> {
    /* a lot of code we don't care about now */
    public function filter(predicate:Predicate<T>):Collection<T> { 
         //build a new collection with all elements e, such that predicate.test(e) == true
    }
    public function remove(e:T):Bool {
         //removes an element from this collection, if contained and returns true, false otherwise
    }
}

Qu'est ce qui a changé? Ce qui a changé, c'est que quelle que soit la valeur qui est désormais donnée, elle filtera explicitement signé le contrat d'être un prédicat. Bien sûr, des programmeurs malveillants ou extrêmement stupides créent des implémentations de l'interface qui ne sont pas exemptes d'effets secondaires et ne sont donc pas des prédicats.
Mais ce qui ne peut plus se produire, c'est que quelqu'un attache une unité logique de données / code, qui, à tort, est interprété comme un prédicat en raison de sa structure externe.

Donc, pour reformuler ce qui a été dit ci-dessus en quelques mots:

  • les interfaces signifient utiliser un typage nominal et donc des relations explicites
  • les délégués signifient utiliser le typage structurel et donc les relations implicites

L'avantage des relations explicites est que vous pouvez en être certain. L'inconvénient est qu'ils nécessitent des frais généraux d'explicitation. Inversement, l'inconvénient des relations implicites (dans notre cas, la signature d'une fonction correspondant à la signature souhaitée), c'est que vous ne pouvez pas vraiment être sûr à 100% que vous pouvez utiliser les choses de cette façon. L'avantage est que vous pouvez établir des relations sans tous les frais généraux. Vous pouvez simplement regrouper rapidement les choses, car leur structure le permet. Voilà ce que signifie une composition facile.
C'est un peu comme LEGO: vous pouvez simplement brancher une figurine LEGO Star Wars sur un bateau pirate LEGO, simplement parce que la structure extérieure le permet. Maintenant, vous pourriez sentir que c'est terriblement mal, ou ce pourrait être exactement ce que vous voulez. Personne ne va vous arrêter.

back2dos
la source
1
Il convient de mentionner que «implémente explicitement l'interface» (sous «sous-typage nominal») n'est pas la même chose que l'implémentation explicite ou implicite des membres de l'interface. En C #, les classes doivent toujours déclarer explicitement les interfaces qu'elles implémentent, comme vous le dites. Mais les membres de l'interface peuvent être implémentés explicitement (par exemple, int IList.Count { get { ... } }) ou implicitement ( public int Count { get { ... } }). Cette distinction n'est pas pertinente pour cette discussion, mais elle mérite d'être mentionnée pour éviter de confondre les lecteurs.
phoog
@phoog: Oui, merci pour l'amendement. Je ne savais même pas que le premier était possible. Mais oui, la façon dont un langage applique réellement l'implémentation de l'interface varie beaucoup. Dans Objective-C par exemple, il ne vous donnera un avertissement que s'il n'est pas implémenté.
back2dos
1
  1. Un modèle de conception d'événement implique une structure qui implique un mécanisme de publication et d'abonnement. Les événements sont publiés par une source, chaque abonné obtient sa propre copie de l'élément publié et est responsable de ses propres actions. Il s'agit d'un mécanisme faiblement couplé car l'éditeur n'a même pas besoin de savoir que les abonnés sont là. Sans parler d'avoir des abonnés n'est pas obligatoire (il peut y en avoir aucun)
  2. la composition est "plus facile" si un délégué est utilisé par rapport à une interface. Les interfaces définissent une instance de classe comme étant un objet "sorte de gestionnaire" alors qu'une instance de construction de composition semble avoir "un gestionnaire", donc l'apparence de l'instance est moins restreinte en utilisant un délégué et devient plus flexible.
  3. D'après les commentaires ci-dessous, j'ai trouvé que j'avais besoin d'améliorer mon message, consultez ce document pour référence: http://bytes.com/topic/c-sharp/answers/252309-interface-vs-delegate
Carlo Kuip
la source
1
3. Pourquoi les délégués auraient-ils besoin de plus de casting et pourquoi un couplage plus étroit serait-il un avantage? 4. Vous n'avez pas besoin d'utiliser des événements pour utiliser des délégués, et avec les délégués, c'est comme tenir une méthode - vous savez que c'est le type de retour et les types d'arguments. De plus, pourquoi une méthode déclarée dans une interface faciliterait-elle la gestion des erreurs qu'une méthode attachée à un délégué?
Yam Marcovic
En cas de spécification d'un délégué, vous avez raison. Cependant, dans le cas de la gestion des événements (du moins c'est ce à quoi ma référence se rapporte), vous devez toujours hériter d'une sorte d'événementArgs pour servir de conteneur pour les types personnalisés, donc au lieu de pouvoir tester en toute sécurité une valeur, vous devriez toujours devez déballer votre type avant de manipuler les valeurs.
Carlo Kuip
Quant à la raison pour laquelle je pense qu'une méthode définie dans une interface permettrait de gérer plus facilement les erreurs, le fait que le compilateur puisse vérifier les types d'exceptions levés à partir de cette méthode. Je sais que ce ne sont pas des preuves convaincantes, mais devrait peut-être être indiqué comme préférence?
Carlo Kuip
1
Tout d'abord, nous parlions de délégués vs interfaces, pas d'événements vs interfaces. Deuxièmement, la création d'un événement lui-même sur un délégué EventHandler n'est qu'une convention, et en aucun cas obligatoire. Mais encore une fois, parler des événements ici manque un peu. Quant à votre deuxième commentaire - les exceptions ne sont pas vérifiées en C #. Vous vous confondez avec Java (qui n'a d'ailleurs même pas de délégués).
Yam Marcovic
Trouvé un fil sur ce sujet expliquant les différences importantes: bytes.com/topic/c-sharp/answers/252309-interface-vs-delegate
Carlo Kuip