Partager via


System.Delegate et le mot clé delegate

Précédent

Cet article décrit les classes de .NET qui prennent en charge les délégués et comment celles-ci sont mappées au delegate mot clé.

Qu’est-ce que les délégués ?

Considérez un délégué comme un moyen de stocker une référence à une méthode, similaire à la façon dont vous pouvez stocker une référence à un objet. Tout comme vous pouvez passer des objets à des méthodes, vous pouvez transmettre des références de méthode à l’aide de délégués. Cela est utile lorsque vous souhaitez écrire du code flexible où différentes méthodes peuvent être « branchées » pour fournir différents comportements.

Par exemple, imaginez que vous disposez d’une calculatrice qui peut effectuer des opérations sur deux nombres. Au lieu d’ajouter, soustraire, multiplier et diviser en méthodes distinctes, vous pouvez utiliser des délégués pour représenter toute opération qui prend deux nombres et retourne un résultat.

Définir des types délégués

Voyons maintenant comment créer des types délégués à l’aide du delegate mot clé. Lorsque vous définissez un type de délégué, vous créez essentiellement un modèle qui décrit le type de méthodes pouvant être stockés dans ce délégué.

Vous définissez un type délégué à l’aide de la syntaxe qui ressemble à une signature de méthode, mais avec le delegate mot clé au début :

// Define a simple delegate that can point to methods taking two integers and returning an integer
public delegate int Calculator(int x, int y);

Ce Calculator délégué peut contenir des références à n’importe quelle méthode qui accepte deux int paramètres et retourne un int.

Examinons un exemple plus pratique. Lorsque vous souhaitez trier une liste, vous devez indiquer à l’algorithme de tri comment comparer les éléments. Voyons comment les délégués contribuent à la méthode List.Sort(). La première étape consiste à créer un type délégué pour l’opération de comparaison :

// From the .NET Core library
public delegate int Comparison<in T>(T left, T right);

Ce Comparison<T> délégué peut contenir des références à n’importe quelle méthode qui :

  • Prend deux paramètres de type T
  • Renvoie une int valeur (généralement -1, 0 ou 1 pour indiquer « inférieur à », « égal à » ou « supérieur à »)

Lorsque vous définissez un type délégué comme celui-ci, le compilateur génère automatiquement une classe dérivée de System.Delegate celle-ci correspond à votre signature. Cette classe gère toute la complexité du stockage et de l’appel des références de méthode pour vous.

Le Comparison type délégué est un type générique, ce qui signifie qu’il peut fonctionner avec n’importe quel type T. Pour plus d’informations sur les génériques, consultez classes et méthodes génériques.

Notez que même si la syntaxe ressemble à la déclaration d’une variable, vous déclarez en fait un nouveau type. Vous pouvez définir des types délégués à l’intérieur de classes, directement à l’intérieur d’espaces de noms ou même dans l’espace de noms global.

Remarque

La déclaration de types délégués (ou d’autres types) directement dans l’espace de noms global n’est pas recommandée.

Le compilateur génère également des gestionnaires d’ajout et de suppression pour ce nouveau type afin que les clients de cette classe puissent ajouter et supprimer des méthodes de la liste d’appel d’une instance. Le compilateur applique que la signature de la méthode en cours d’ajout ou de suppression correspond à la signature utilisée lors de la déclaration du type délégué.

Déclarer des instances de délégués

Après avoir défini le type délégué, vous pouvez créer des instances (variables) de ce type. Considérez cela comme la création d’un « emplacement » dans lequel vous pouvez stocker une référence à une méthode.

Comme toutes les variables en C#, vous ne pouvez pas déclarer d’instances de délégué directement dans un espace de noms ou dans l’espace de noms global.

// Inside a class definition:
public Comparison<T> comparator;

Le type de cette variable est Comparison<T> (le type délégué que vous avez défini précédemment) et le nom de la variable est comparator. À ce stade, comparator ne pointe pas encore vers une méthode : c'est comme un emplacement vide en attente d'être rempli.

Vous pouvez également déclarer des variables déléguées en tant que variables locales ou paramètres de méthode, comme n’importe quel autre type de variable.

Appeler des délégués

Une fois que vous avez une instance de délégué qui pointe vers une méthode, vous pouvez invoquer cette méthode via le délégué. Vous invoquez les méthodes qui se trouvent dans la liste d'invocation d'un délégué en appelant ce délégué comme s'il s'agissait d'une méthode.

Voici comment la Sort() méthode utilise le délégué de comparaison pour déterminer l’ordre des objets :

int result = comparator(left, right);

Dans cette ligne, le code appelle la méthode attachée au délégué. Vous traitez la variable de délégué comme s’il s’agissait d’un nom de méthode et appelez-la à l’aide de la syntaxe d’appel de méthode normale.

Toutefois, cette ligne de code fait une hypothèse dangereuse : elle suppose qu’une méthode cible a été ajoutée au délégué. Si aucune méthode n’a été attachée, la ligne ci-dessus provoquerait une exception NullReferenceException. Les modèles utilisés pour résoudre ce problème sont plus sophistiqués qu’une simple vérification null et sont abordés plus loin dans cette série.

Affecter, ajouter et supprimer des cibles d’appel

Vous savez maintenant comment définir des types délégués, déclarer des instances de délégué et appeler des délégués. Mais comment connecter une méthode à un délégué ? C’est là qu'entre en jeu la délégation d’affectation.

Pour utiliser un délégué, vous devez lui attribuer une méthode. La méthode que vous affectez doit avoir la même signature (même paramètre et type de retour) que le type délégué définit.

Voyons un exemple pratique. Supposons que vous souhaitez trier une liste de chaînes par leur longueur. Vous devez créer une méthode de comparaison qui correspond à la Comparison<string> signature de délégué :

private static int CompareLength(string left, string right) =>
    left.Length.CompareTo(right.Length);

Cette méthode prend deux chaînes et retourne un entier indiquant quelle chaîne est « plus grande » (plus longue dans ce cas). La méthode est déclarée comme privée, qui est parfaitement fine. Vous n'avez pas besoin que la méthode appartienne à votre interface publique pour l'utiliser avec un délégué.

Vous pouvez maintenant passer cette méthode à la List.Sort() méthode :

phrases.Sort(CompareLength);

Notez que vous utilisez le nom de la méthode sans parenthèses. Cela indique au compilateur de convertir la référence de méthode en délégué qui peut être appelé ultérieurement. La Sort() méthode appelle votre CompareLength méthode chaque fois qu’elle doit comparer deux chaînes.

Vous pouvez également être plus explicite en déclarant une variable déléguée et en lui affectant la méthode :

Comparison<string> comparer = CompareLength;
phrases.Sort(comparer);

Les deux approches effectuent la même chose. La première approche est plus concise, tandis que la seconde rend l’attribution de délégué plus explicite.

Pour les méthodes simples, il est courant d’utiliser des expressions lambda au lieu de définir une méthode distincte :

Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length);
phrases.Sort(comparer);

Les expressions lambda fournissent un moyen compact de définir des méthodes simples inline. L’utilisation d’expressions lambda pour les cibles déléguées est abordée plus en détail dans une section ultérieure.

Jusqu’à présent, les exemples montrent les délégués avec une seule méthode cible. Toutefois, les objets délégués peuvent prendre en charge les listes d’appel qui ont plusieurs méthodes cibles attachées à un seul objet délégué. Cette fonctionnalité est particulièrement utile pour les scénarios de gestion des événements.

Classes Delegate et MulticastDelegate

En arrière-plan, les fonctionnalités de délégué que vous utilisez sont basées sur deux classes clés dans le .NET Framework : Delegate et MulticastDelegate. Vous ne travaillez généralement pas directement avec ces classes, mais elles fournissent la base qui fait fonctionner les délégués.

La classe System.Delegate et sa sous-classe directe System.MulticastDelegate fournissent le support de cadre pour la création de délégués, l'enregistrement de méthodes en tant que cibles de délégués, et l'invocation de toutes les méthodes enregistrées auprès d’un délégué.

Voici un détail de conception intéressant : System.Delegate et System.MulticastDelegate ne sont pas eux-mêmes des types délégués que vous pouvez utiliser. Au lieu de cela, ils servent de classes de base pour tous les types de délégués spécifiques que vous créez. Le langage C# vous empêche d’hériter directement de ces classes : vous devez utiliser le mot clé delegate à la place.

Lorsque vous utilisez le mot-clé delegate pour déclarer un type délégué, le compilateur C# crée automatiquement une classe dérivée de MulticastDelegate avec votre signature spécifique.

Pourquoi cette conception ?

Cette conception a ses racines dans la première version de C# et .NET. L’équipe de conception a eu plusieurs objectifs :

  1. Sécurité de type : l’équipe souhaitait s’assurer que le langage garantissait la sécurité de type lors de l’utilisation de délégués. Cela signifie que les délégués sont appelés avec le type et le nombre d’arguments corrects, et que les types de retour sont vérifiés correctement au moment de la compilation.

  2. Performances : si le compilateur génère des classes de délégué concrètes qui représentent des signatures de méthode spécifiques, le runtime peut optimiser les appels de délégués.

  3. Simplicité : les délégués ont été inclus dans la version 1.0 .NET, qui était avant l’introduction des génériques. La conception devait s'adapter aux contraintes de l'époque.

La solution était de faire en sorte que le compilateur crée les classes de délégué concrètes qui correspondent à vos signatures de méthode, garantissant la sécurité des types tout en masquant la complexité pour vous.

Utilisation des méthodes déléguées

Même si vous ne pouvez pas créer de classes dérivées directement, vous utiliserez parfois des méthodes définies sur les classes Delegate et MulticastDelegate. Voici les plus importants à connaître :

Chaque délégué avec lequel vous travaillez est dérivé de MulticastDelegate. Un délégué « multicast » signifie que plusieurs méthodes cibles peuvent être appelées lors d’un appel par l’intermédiaire d’un délégué. La conception d’origine envisage de faire une distinction entre les délégués qui ne pouvaient appeler qu’une seule méthode et des délégués qui pouvaient appeler plusieurs méthodes. Dans la pratique, cette distinction s’est avérée moins utile qu’à l’origine, donc tous les délégués dans .NET prennent en charge plusieurs méthodes cibles.

Les méthodes les plus couramment utilisées lors de l’utilisation des délégués sont les suivantes :

  • Invoke() : appelle toutes les méthodes attachées au délégué
  • BeginInvoke() / EndInvoke(): utilisé pour les modèles d’appel asynchrones (bien qu’il async/await soit désormais préféré)

Dans la plupart des cas, vous n'allez pas appeler ces méthodes directement. Au lieu de cela, vous allez utiliser la syntaxe d’appel de méthode sur la variable déléguée, comme illustré dans les exemples ci-dessus. Toutefois, comme vous le verrez plus loin dans cette série, il existe des modèles qui fonctionnent directement avec ces méthodes.

Résumé

Maintenant que vous avez vu comment la syntaxe du langage C# est mappée aux classes .NET sous-jacentes, vous êtes prêt à explorer la façon dont les délégués fortement typés sont utilisés, créés et appelés dans des scénarios plus complexes.

Prochain