Délégués

Références

L'actualité

Librairie

L'information

Introduction

Un délégué est un type qui représente des références aux méthodes avec une liste de paramètres et un type de retour particuliers. Lorsque vous instanciez un délégué, vous pouvez associer son instance à toute méthode ayant une signature et un type de retour compatibles. Vous pouvez appeler la méthode par le biais l'instance de délégué.

Les délégués sont utilisés pour passer des méthodes comme arguments à d'autres méthodes. Les gestionnaires d'événements sont tout simplement des méthodes appelées par le biais de délégués. Vous créez une méthode personnalisée, et une classe telle qu'un contrôle Windows peut appeler votre méthode lorsqu'un certain événement se produit. L'exemple suivant illustre une déclaration de délégué :

Toute méthode de n'importe quelle classe ou structure accessible qui correspond au type de délégué, peut être assignée au délégué. La méthode peut être une méthode d'instance ou statique. Cela permet de modifier par programme les appels de méthode, mais également d'insérer du nouveau code dans les classes existantes.

Dans le contexte de surcharge de méthodes, la signature d'une méthode n'inclut pas la valeur de retour. Mais dans le contexte des délégués, la signature inclut la valeur de retour. En d'autres termes, une méthode doit avoir le même type de retour que le délégué.

Cette capacité à faire référence à une méthode en tant que paramètre fait des délégués les instruments idéaux pour définir des méthodes de rappel. Par exemple, une référence à une méthode qui compare deux objets peut être passée comme argument à un algorithme de tri. étant donné que le code de comparaison est dans une procédure distincte, l'algorithme de tri peut être écrit de façon plus générale.

Vue d'ensemble des délégués

Les délégués ont les propriétés suivantes :

  • Les délégués sont comparables aux pointeurs de fonction C++, sauf que les délégués sont totalement orientés objet, et contrairement aux pointeurs C++ vers les fonctions membres, les délégués encapsulent une instance d'objet et une méthode.
  • Les délégués permettent aux méthodes d'être transmises comme des paramètres.
  • Les délégués peuvent être utilisés pour définir des méthodes de rappel.
  • Les délégués peuvent être chaînés ; par exemple, plusieurs méthodes peuvent être appelées sur un seul événement.
  • Les méthodes ne doivent pas correspondre exactement au type du délégué. Pour plus d'informations, consultez Utilisation de la variance dans les délégués.
  • C# version 2.0 a introduit le concept de Méthodes anonymes, qui permet de passer des blocs de code en tant que paramètres à la place d'une méthode définie séparément. C# 3.0 a introduit les expressions lambda comme un moyen plus concis d'écrire des blocs de code inline. Les méthodes anonymes et les expressions lambda (dans certains contextes) sont toutes deux compilées en types de délégué. Ces fonctionnalités sont désormais conjointement désignées par l'expression "fonctions anonymes". Pour plus d'informations sur les expressions lambda, consultez Fonctions anonymes.


Utilisation de délégués

Un délégué est un type qui encapsule sans risque une méthode ; il est similaire à un pointeur de fonction en C et C++. Toutefois, à la différence des pointeurs de fonction C, les délégués sont orientés objet, de type sécurisé et sûrs. Le type d'un délégué est défini par le nom du délégué. L'exemple suivant déclare un délégué nommé Del qui peut encapsuler une méthode acceptant une chaîne (string) comme argument et qui retourne void :

Un objet de délégué est normalement construit en fournissant le nom de la méthode que le délégué encapsule, ou avec une méthode anonyme. Une fois qu'un délégué est instancié, un appel de méthode fait au délégué est transmis par le délégué à cette méthode. Les paramètres passés au délégué par l'appelant sont passés à la méthode et la valeur de retour de la méthode, le cas échéant, est retournée à l'appelant par le délégué. Cette opération est connue sous le nom d'appel du délégué. Un délégué instancié peut être appelé comme s'il s'agissait de la méthode encapsulée elle-même. Par exemple :


Les types délégués sont dérivés de la classe Delegate du .NET Framework. Les types délégués sont scellés (sealed), c'est-à-dire qu'ils ne peuvent pas être faire l'objet d'une dérivation, et il n'est pas possible de dériver des classes personnalisées à partir de Delegate. Étant donné que le délégué instancié est un objet, il peut être passé comme paramètre ou assigné à une propriété. Cela permet à une méthode d'accepter un délégué comme paramètre et d'appeler le délégué ultérieurement. Cette opération est connue sous le nom de rappel asynchrone et constitue une méthode courante pour notifier un appelant quand un long processus s'est achevé. Lorsqu'un délégué est ainsi utilisé, le code qui utilise le délégué n'a pas besoin de connaître l'implémentation de la méthode utilisée. La fonctionnalité est semblable à l'encapsulation que fournissent les interfaces.

Une autre utilisation courante des rappels consiste à définir une méthode de comparaison personnalisée et à passer ce délégué à une méthode de tri. Le code de l'appelant peut ainsi faire partie de l'algorithme de tri. L'exemple de méthode suivant utilise le type Del comme paramètre :

Vous pouvez ensuite passer le délégué créé ci-dessus à cette méthode :

et vous recevez le résultat suivant sur la console :

The number is: 3

En utilisant le délégué comme abstraction, MethodWithCallback n'a pas besoin d'appeler la console directement. La méthode n'a pas à être conçue en l'associant obligatoirement à une console. En effet, MethodWithCallback se contente de préparer une chaîne et de la passer à une autre méthode. Cela est particulièrement efficace, car une méthode déléguée peut utiliser n'importe quel nombre de paramètres.

Quand un délégué est construit pour encapsuler une méthode d'instance, le délégué fait référence à l'instance et à la méthode. Un délégué n'ayant aucune connaissance du type d'instance hormis la méthode qu'il encapsule, un délégué peut faire référence à n'importe quel type d'objet tant qu'il existe une méthode sur l'objet correspondant à la signature du délégué. Quand un délégué est construit pour encapsuler une méthode statique, il fait uniquement référence à la méthode. Prenons les déclarations suivantes :

Avec la méthode statique DelegateMethod montrée précédemment, nous avons désormais trois méthodes pouvant être encapsulées par une instance Del.

Un délégué peut appeler plusieurs méthodes quand il est appelé. Cette opération se nomme multidiffusion. L'ajout d'une méthode supplémentaire à la liste des méthodes du délégué "la liste d'invocation" nécessite simplement l'ajout de deux délégués à l'aide des opérateurs d'addition ou d'assignation d'addition (+ ou +=), par exemple :

à ce stade, allMethodsDelegate contient trois méthodes dans sa liste d'invocation : Method1, Method2 et DelegateMethod. Les trois délégués initiaux, d1, d2 et d3, restent inchangés. Lors de l'appel de allMethodsDelegate, les trois méthodes sont appelées dans l'ordre. Si le délégué utilise des paramètres de référence, la référence est passée séquentiellement à chacune des trois méthodes et toute modification apportée par une méthode est visible dans la méthode suivante. Quand l'une des méthodes lève une exception qui n'est pas interceptée dans la méthode, cette exception est passée à l'appelant du délégué et aucune des méthodes suivantes dans la liste d'invocation n'est appelée. Si le délégué a une valeur de retour et/ou des paramètres out, il retourne la valeur de retour et les paramètres de la dernière méthode appelée. Pour supprimer une méthode de la liste d'invocation, utilisez l'opérateur de décrémentation ou d'assignation de décrémentation (- ou -=), par exemple :

Étant donné que les types délégués sont dérivés de System.Delegate, les méthodes et les propriétés définies par cette classe peuvent être appelées sur le délégué. Par exemple, pour rechercher le nombre de méthodes dans la liste d'invocation d'un délégué, vous pouvez écrire :

Les délégués ayant plusieurs méthodes dans leur liste d'invocation dérivent de MulticastDelegate, qui est une sous-classe de System.Delegate. Le code ci-dessus fonctionne dans les deux cas, car les deux classes prennent en charge GetInvocationList.

Les délégués multicast sont largement utilisés dans la gestion des événements. Les objets sources d'événements envoient des notifications d'événement aux objets destinataires qui se sont inscrits pour recevoir cet événement. Pour s'inscrire à un événement, le destinataire crée une méthode conçue pour gérer l'événement, puis crée un délégué pour cette méthode et passe le délégué à la source d'événements. La source appelle le délégué quand l'événement se produit. Le délégué appelle ensuite la méthode de gestion d'événement sur le destinataire, en fournissant les données d'événement. Le type délégué d'un événement donné est défini par la source de l'événement. Pour plus d'informations, consultez événements.

La comparaison de délégués de deux types différents assignés au moment de la compilation provoque une erreur de compilation. Si les instances de délégué sont statiquement du type System.Delegate, la comparaison est alors autorisée, mais retourne la valeur false à l'exécution, par exemple :


Méthodes nommées

Un délégué peut être associé à une méthode nommée. Quand vous instanciez un délégué à l'aide d'une méthode nommée, la méthode est passée en tant que paramètre, par exemple :

C'est ce qui s'appelle utiliser une méthode nommée. Les délégués construits avec une méthode nommée peuvent encapsuler une méthode static ou une méthode d'instance. Les méthodes nommées représentent la seule façon d'instancier un délégué dans les versions précédentes de C#. Toutefois, dans une situation où la création d'une méthode constitue une charge de travail non souhaitée, C# vous permet d'instancier un délégué et de spécifier immédiatement un bloc de code que le délégué traite quand il est appelé. Le bloc peut contenir une expression lambda ou une méthode anonyme.

Notes

La méthode que vous passez en tant que paramètre de délégué doit avoir la même signature que la déclaration du délégué.

Une instance de délégué encapsule soit une méthode statique soit une méthode d'instance.

Même si le délégué peut utiliser un paramètre out, nous ne recommandons pas son utilisation avec les délégués d'événement multicast, car vous ne pouvez pas savoir quel délégué sera appelé.

Exemple

L'exemple suivant est un exemple simple de déclaration et d'utilisation d'un délégué. Notez que le délégué, Del, et la méthode associée, MultiplyNumbers, ont la même signature

Dans l'exemple suivant, un délégué est associé à une méthode statique et à une méthode d'instance, et retourne des informations spécifiques à partir de chacune.


Délégués multicast

Cet exemple explique comment créer des délégués multicast. Une propriété utile des objets délégués est que plusieurs objets peuvent être assignés à une instance de délégué à l'aide de l'opérateur +. Le délégué multicast contient une liste des délégués assignés. Quand le délégué multicast est appelé, il appelle les délégués dans la liste, dans l'ordre. Seuls des délégués de même type peuvent être combinés.

Vous pouvez utiliser l'opérateur - pour supprimer un délégué de composant d'un délégué multicast.


Utiliser un délégué

Dans C# 1.0 et versions ultérieures, vous pouvez déclarer des délégués comme illustré dans l'exemple suivant.


C# 2.0 offre un moyen plus simple d'écrire la déclaration précédente, comme illustré dans l'exemple suivant.

Dans C# 2.0 et versions ultérieures, vous pouvez aussi utiliser une méthode anonyme pour déclarer et initialiser un délégué, comme illustré dans l'exemple suivant.

Dans C# 3.0 et versions ultérieures, vous pouvez aussi déclarer et instancier des délégués à l'aide d'une expression lambda, comme illustré dans l'exemple suivant.

L'exemple suivant illustre la déclaration, l'instanciation et l'utilisation d'un délégué. La classe BookDB encapsule une base de données de librairie qui tient à jour un inventaire des livres. Elle expose une méthode, ProcessPaperbackBooks, qui recherche tous les livres de poche dans la base de données et appelle un délégué pour chacun d'entre eux. Le type delegate qui est utilisé se nomme ProcessBookDelegate. La classe Test utilise cette classe pour imprimer les titres et le prix moyen des livres de poche.

L'utilisation des délégués favorise une bonne séparation des fonctionnalités entre la base de données de librairie et le code client. Le code client n'a aucune connaissance de la façon dont les livres sont stockés, ni de la façon dont le code librairie trouve les livres de poche. Le code librairie n'a aucune connaissance du traitement effectué sur les livres de poche une fois qu'il les a trouvés.

Exemple



Programmation fiable

Déclaration d'un délégué. L'instruction suivante déclare un nouveau type délégué.

Chaque type délégué décrit le nombre et les types des arguments, ainsi que le type de la valeur de retour des méthodes qu'il peut encapsuler. Chaque fois qu'un nouvel ensemble de types d'arguments ou de types de valeur de retour est nécessaire, un nouveau type délégué doit être déclaré.

Instanciation d'un délégué.

Une fois que vous avez déclaré un type délégué, vous devez créer un objet délégué et l'associer à une méthode particulière. Pour cela, dans l'exemple précédent vous passez la méthode PrintTitle à la méthode ProcessPaperbackBooks, comme illustré dans l'exemple suivant :

Cela crée un nouvel objet délégué associé à la méthode statique Test.PrintTitle. De même, la méthode non statique AddBookToTotal sur l'objet totaller est passée comme dans l'exemple suivant :

Dans les deux cas, un nouvel objet délégué est passé à la méthode

ProcessPaperbackBooks

Une fois un délégué créé, la méthode à laquelle il est associé ne change jamais. Les objets délégué sont immuables.

Appel d'un délégué.

Une fois créé, un objet délégué est généralement passé à un autre code qui appellera le délégué. Pour appeler un objet délégué, vous devez utiliser le nom de l'objet délégué, suivi des arguments entre parenthèses à passer au délégué. Voici un exemple d'appel de délégué :

Vous pouvez appeler un délégué de manière synchrone, comme dans cet exemple, ou de manière asynchrone à l'aide des méthodes BeginInvoke et EndInvoke.