
C# 14 introduit les membres d'extension. C# dispose depuis longtemps de méthodes d'extension et la nouvelle syntaxe des membres d'extension s'appuie sur cette fonctionnalité familière. La dernière version ajoute des méthodes d'extension statiques et des propriétés d'extension statiques et d'instance. Nous publierons d'autres types de membres à l'avenir.
Les membres d'extension introduisent également une syntaxe alternative pour les méthodes d'extension. Cette nouvelle syntaxe est facultative et vous n'avez pas besoin de modifier vos méthodes d'extension existantes. Dans cet article, nous appellerons la syntaxe existante des méthodes d'extension "méthodes d'extension paramètre This" et la nouvelle syntaxe "membres d'extension".
Quel que soit le style, les membres d'extension ajoutent des fonctionnalités aux types. C'est particulièrement utile si vous n'avez pas accès au code source du type ou s'il s'agit d'une interface. Si vous n'aimez pas utiliser !list.Any(), vous pouvez créer votre propre méthode d'extension IsEmpty(). Depuis la dernière version, vous pouvez en faire une propriété et l'utiliser comme n'importe quelle autre propriété du type :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | // An extension property `IsEmpty` is declared in a separate class public void Operate(IEnumerable<string> strings) { if (strings.IsEmpty) { return; } // Do the operation } |
En utilisant la nouvelle syntaxe, vous pouvez également ajouter des extensions qui fonctionnent comme des propriétés static et des méthodes sur le type sous-jacent.
Ce billet examine les avantages de la syntaxe actuelle, les défis de conception que nous avons résolus et quelques scénarios plus profonds. Si vous n'avez jamais écrit de méthode d'extension, vous verrez à quel point il est facile de commencer.
Création de membres d'extension
Voici des exemples d'écriture d'extensions utilisant la syntaxe d'extension de ce paramètre et la nouvelle syntaxe :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public static class MyExtensions { public static IEnumerable<int> ValuesLessThan(this IEnumerable<int> source, int threshold) => source.Where(x => x < threshold); extension(IEnumerable<int> source) { public IEnumerable<int> ValuesGreaterThan(int threshold) => source.Where(x => x > threshold); public IEnumerable<int> ValuesGreaterThanZero => source.ValuesGreaterThan(0); } } |
Les membres d'extension ont besoin de deux types d'informations : le récepteur auquel le membre doit s'appliquer et les paramètres dont il a besoin s'il s'agit d'une méthode. Les méthodes d'extension paramètre This, comme ValueLessThan, rassemblent ces informations dans la liste des paramètres, en préfixant le récepteur par this.
La nouvelle syntaxe des méthodes d'extension sépare le récepteur en le plaçant dans le bloc d'extension. Cela permet d'accueillir le récepteur lorsque le membre n'a pas de paramètres, comme c'est le cas pour les propriétés d'extension.
Dans le bloc d'extension, le code ressemble à ce qu'il serait si les membres étaient placés sur le type sous-jacent.
Un autre avantage de la syntaxe des membres d'extension est le regroupement des extensions qui s'appliquent au même récepteur. Le nom, le type, les paramètres du type générique, les contraintes des paramètres du type, les attributs et les modificateurs du récepteur sont tous appliqués dans le bloc d'extension, ce qui évite les répétitions. Plusieurs blocs d'extension peuvent être utilisés lorsque ces détails diffèrent.
La classe statique qui la contient peut contenir n'importe quelle combinaison de blocs d'extension, de méthodes d'extension de ce paramètre et d'autres types de membres statiques. Même dans les nouveaux projets qui n'utilisent que la nouvelle syntaxe des membres d'extension, vous pouvez également déclarer d'autres méthodes d'aide statiques.
Le fait d'autoriser les blocs d'extension, les méthodes d'extension de ce paramètre et d'autres membres statiques dans la même classe statique minimise les modifications à apporter pour ajouter de nouveaux types de membres d'extension. Si vous souhaitez simplement ajouter une propriété, il vous suffit d'ajouter un bloc d'extension à votre classe statique existante. Comme vous pouvez utiliser les deux styles de syntaxe ensemble, il n'est pas nécessaire de convertir vos méthodes existantes pour tirer parti des nouveaux types de membres.
Les blocs d'extension vous donnent toute liberté pour organiser votre code comme bon vous semble. Il ne s'agit pas seulement d'une préférence personnelle ou d'équipe. Les membres d'extension résolvent de nombreux types de problèmes différents qui sont mieux gérés avec différentes dispositions de classes statiques.
Lors de la conception des membres d'extension, nous avons examiné le code dans des endroits publics tels que GitHub et avons constaté d'énormes variations dans la façon dont les gens organisent les méthodes d'extension de ce paramètre. Les modèles les plus courants sont les classes statiques qui contiennent des méthodes d'extension pour un type spécifique (comme StringExtensions), le regroupement de toutes les extensions d'un projet dans une seule classe statique, et le regroupement de plusieurs surcharges avec différents types de récepteurs dans une classe statique. Il s'agit là de la bonne approche pour certains scénarios.
La façon dont les extensions sont organisées dans les classes statiques qui les contiennent est importante car le nom de la classe statique est utilisé pour la désambiguïsation. La désambiguïsation n'est pas courante, mais lorsqu'une ambiguïté survient, l'utilisateur a besoin d'une solution. Le déplacement d'une extension vers une classe statique portant un nom différent constitue un changement radical, car quelqu'un, quelque part, utilise le nom de la classe statique pour la désambiguïser.
Défis de conception pour les membres d'extension
L'équipe de conception de C# a envisagé de prendre en charge d'autres types de membres à plusieurs reprises depuis l'introduction des méthodes d'extension de ce paramètre. Il s'agit d'un problème difficile car les défis sont créés par les réalités sous-jacentes :
- Les méthodes d'extension sont profondément ancrées dans notre écosystème - elles sont nombreuses et très utilisées. Toute modification de la syntaxe des extensions doit être naturelle pour les développeurs qui les utilisent.
- La conversion d'une méthode d'extension à une nouvelle syntaxe ne doit pas casser le code qui l'utilise.
- Les développeurs organisent leurs extensions en fonction de leur scénario.
- Le récepteur doit être déclaré quelque part - les méthodes ont des paramètres, mais les propriétés et la plupart des autres types de membres n'en ont pas.
- L'approche actuelle de la désambiguïsation est bien connue et basée sur le nom de la classe statique qui la contient.
- Les méthodes d'extension sont appelées beaucoup plus souvent qu'elles ne sont créées ou modifiées.
La section suivante examine plus en détail les récepteurs, la désambiguïsation et l'équilibre entre les besoins des auteurs d'extensions et ceux des développeurs qui utilisent les extensions.
Récepteurs
Lorsque vous appelez une méthode d'extension, le compilateur réécrit l'appel, ce que l'on appelle la descente. Dans le code ci-dessous, l'appel de la méthode sur le récepteur (pour assigner x1) est abaissé à l'appel de la méthode statique avec le récepteur comme premier paramètre, ce qui est équivalent au code qui définit x2 :
Code : | Sélectionner tout |
1 2 3 | int[] list = [ 1, 3, 5, 7, 9 ]; var x1 = list.ValuesLessThan(3); var x2 = MyExtensions.ValuesLessThan(list, 3); |
Dans la déclaration d'une méthode d'extension paramètre this, le récepteur est le premier paramètre et est préfixé par this :
Code : | Sélectionner tout |
1 2 3 | public static class MyExtensions { public static IEnumerable<int> ValuesLessThan(this IEnumerable<int> source, int threshold) ... |
Dans cette déclaration, le récepteur est this IEnumerable<int> source et int threshold est un paramètre de la méthode. Cela fonctionne bien pour les méthodes, mais les propriétés et la plupart des autres types de membres n'ont pas de paramètres pouvant contenir le récepteur. Les membres d'extension résolvent ce problème en plaçant le récepteur dans le bloc d'extension. La méthode ValueLessThan dans la nouvelle syntaxe serait la suivante :
Code : | Sélectionner tout |
1 2 3 4 5 | public static class MyExtensions { extension(IEnumerable<int> source) { public IEnumerable<int> ValuesLessThan(int threshold) ... |
Le compilateur réduit cette méthode à une méthode statique identique à la précédente déclaration de méthode d'extension paramètre This, en utilisant le paramètre récepteur du bloc d'extension. La version abaissée est disponible pour désambiguïser lorsque vous rencontrez des signatures ambiguës. Cette approche garantit également que les méthodes d'extension écrites avec la syntaxe paramètre This ou la nouvelle syntaxe de membre d'extension fonctionnent de la même manière aux niveaux source et binaire.
Propriétés inférieures aux méthodes get et set. Considérons cette propriété d'extension :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 | public static class MyExtensions { extension(IEnumerable<int> source) { public IEnumerable<int> ValuesGreaterThanZero { get { ... |
Cette propriété est ramenée à une méthode get dont le seul paramètre est le type du récepteur :
Code : | Sélectionner tout |
1 2 3 | public static class MyExtensions { public static IEnumerable<int> get_ValuesGreaterThanZero(this IEnumerable<int> source) ... |
Pour les méthodes et les propriétés, le type, le nom, les paramètres du type générique, les contraintes des paramètres de type, les attributs et les modificateurs du bloc d'extension sont tous copiés dans le paramètre récepteur.
Désambiguïsation
En général, on accède aux extensions en tant que membres du récepteur, comme n'importe quelle autre propriété ou méthode, telle que list.ValuesGreaterThan(3). Cette approche simple fonctionne bien dans la plupart des cas. Il arrive parfois qu'une ambiguïté survienne parce que plusieurs extensions avec des signatures valides sont disponibles. Les ambiguïtés sont rares, mais lorsqu'elles surviennent, l'utilisateur a besoin d'un moyen de les résoudre.
La désambiguïsation des méthodes d'extension de ce paramètre se fait en appelant directement la méthode d'extension statique. La désambiguïsation de la syntaxe des nouveaux membres d'extension se fait en appelant les méthodes abaissées créées par le compilateur :
Code : | Sélectionner tout |
1 2 3 4 | var list = new List<int> { 1, 2, 3, 4, 5 }; var x1 = MyExtensions.ValuesLessThan(list, 3); var x2 = MyExtensions.ValuesGreaterThan(list, 3); var x3 = MyExtensions.get_ValuesGreaterThanZero(list); |
L'utilisation d'une classe statique est cohérente avec les méthodes d'extension traditionnelles et avec l'approche que nous pensons que les développeurs attendent. Lorsque le nom de la classe statique est saisi, IntelliSense affiche les membres abaissés, tels que get_ValuesGreaterThanZero.
Priorité aux utilisateurs d'extensions
Nous pensons que la chose la plus importante pour les utilisateurs d'extensions est qu'ils n'aient jamais besoin de penser à la façon dont une extension est créée. Plus précisément, ils ne devraient pas être en mesure de faire la différence entre les méthodes d'extension qui utilisent la syntaxe paramètre this et la syntaxe du nouveau style, les méthodes d'extension existantes continuent de fonctionner et les utilisateurs ne sont pas perturbés si l'auteur met à jour une méthode d'extension paramètres This avec la nouvelle syntaxe.
L'équipe C# étudie depuis de nombreuses années la possibilité d'ajouter d'autres types de membres d'extension. Il n'existe pas de syntaxe parfaite pour cette fonctionnalité. Bien que nos objectifs soient nombreux, nous avons décidé de donner la priorité à l'expérience des développeurs utilisant des extensions, puis à la clarté et à la flexibilité pour les auteurs d'extensions, et enfin à la brièveté de la syntaxe pour la création de membres d'extension.
Méthodes et propriétés statiques
Les membres d'extension statiques sont pris en charge et sont un peu différents parce qu'il n'y a pas de récepteur. Le paramètre du bloc d'extension n'a pas besoin de nom, et s'il en a un, il est ignoré :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 | public static class MyExtensions { extension<T>(List<T>) { public static List<T> Create() => []; } |
Génériques
Tout comme les méthodes d'extension de ce paramètre, les membres d'extension peuvent avoir des génériques ouverts ou concrets, avec ou sans contraintes :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 | public static class MyExtensions { extension<T>(IEnumerable<T> source) where T : IEquatable<T> { public IEnumerable<T> ValuesEqualTo(T threshold) => source.Where(x => x.Equals(threshold)); } |
Les contraintes génériques permettent de mettre à jour les exemples précédents pour gérer tout type numérique prenant en charge l'interface INumber<T> :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public static class MyExtensions { extension<T>(IEnumerable<T> source) where T : INumber<T> { public IEnumerable<T> ValuesGreaterThan(T threshold) => source.Where(x => x > threshold); public IEnumerable<T> ValuesGreaterThanZero => source.ValuesGreaterThan(T.Zero); public IEnumerable<TResult> SelectGreaterThan<TResult>( T threshold, Func<T, TResult> select) => source .ValuesGreaterThan(threshold) .Select(select); } |
La méthode SelectGreaterThan possède également un paramètre de type générique <TResult>, qui est déduit du délégué transmis au paramètre select.
Certaines méthodes d'extension ne peuvent pas être portées
Les règles relatives à la nouvelle syntaxe des membres d'extension auront pour conséquence que quelques méthodes d'extension de type paramètre This devront conserver leur syntaxe actuelle.
L'ordre des paramètres de type générique dans la forme abaissée de la nouvelle syntaxe est le suivant : les paramètres de type récepteur suivis des paramètres de type méthode. Par exemple, SelectGreaterThan ci-dessus serait abaissé à :
Code : | Sélectionner tout |
1 2 3 4 | public static class MyExtensions { public static IEnumerable<TResult> SelectGreaterThan<T, TResult>( this IEnumerable<T> source, T threshold, Func<T, TResult> select) ... |
Si les paramètres de type du récepteur n'apparaissent pas en premier, la méthode ne peut pas être portée à la nouvelle syntaxe. Il s'agit d'un exemple de méthode d'extension avec ce paramètre qui ne peut pas être portée :
Code : | Sélectionner tout |
1 2 3 4 | public static class MyExtensions { public static IEnumerable<TResult> SelectLessThan<TResult, T>( this IEnumerable<T> source, T threshold, Func<T, TResult> select) ... |
De même, il n'est pas possible de porter la méthode vers la nouvelle syntaxe si un paramètre de type du récepteur a une contrainte qui dépend d'un paramètre de type du membre. Nous sommes en train de réexaminer la possibilité de supprimer cette restriction en raison du retour d'information.
Nous pensons que ces scénarios seront rares. Les membres d'extension qui rencontrent ces limitations continueront à travailler en utilisant la syntaxe paramètre This.
Un petit problème de nomenclature
L'équipe de conception de C# fait souvent référence aux membres d'extension en termes de statique et d'instance, de méthodes et de propriétés. Nous entendons par là des méthodes et des propriétés qui se comportent comme s'il s'agissait de méthodes et de propriétés statiques ou d'instance du type sous-jacent. Ainsi, nous considérons les méthodes d'extension de ce paramètre comme des méthodes d'extension d'instance. Mais cela peut prêter à confusion car elles sont déclarées comme des méthodes static dans une classe static. Nous espérons que le fait d'être conscient de cette confusion possible vous aidera à comprendre la proposition, les articles et les présentations.
Résumé
La création de membres d'extension a été un long voyage et nous avons exploré de nombreuses conceptions. Certaines nécessitaient la répétition du récepteur sur chaque membre, d'autres avaient un impact sur la désambiguïsation, d'autres encore imposaient des restrictions sur la manière dont vous organisiez vos membres d'extension, et d'autres enfin créaient une rupture si vous mettiez à jour la nouvelle syntaxe. Certaines avaient des implémentations compliquées. D'autres encore ne ressemblaient tout simplement pas au langage C#.
La nouvelle syntaxe des membres d'extension préserve l'énorme quantité de méthodes d'extension paramètres This existantes tout en introduisant de nouveaux types de membres d'extension. Elle offre une syntaxe alternative pour les méthodes d'extension qui est cohérente avec les nouveaux types de membres et entièrement interchangeable avec la syntaxe paramètre This.
Pour en savoir plus sur les membres d'extension, consultez l'Unboxing de la Preview 3 de C# 14 et découvrez toutes les nouvelles fonctionnalités sur What's new in C# 14.
Nous avons entendu les commentaires selon lesquels le niveau supplémentaire de bloc et d'indentation est surprenant et nous voulions partager la façon dont nous sommes arrivés à ces décisions de conception.
Source : C# 14 – Exploring extension members, par Kathleen Dollard
Et vous ?

Voir aussi :


Vous avez lu gratuitement 2 articles depuis plus d'un an.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.