Une méthode d'extension ajoute une méthode à un type existant sans le modifier, sans en hériter et sans l'envelopper. Vous écrivez une méthode statique ordinaire, marquez son premier paramètre avec this, et les appelants l'utilisent avec la syntaxe à point comme si le type l'avait toujours eue :
Sortie :
Extension methods let yo...
12
short
Extension...
title.Truncate(24) se compile exactement en StringExtensions.Truncate(title, 24). La syntaxe à point n'est qu'une commodité : la méthode reste statique, vit toujours dans StringExtensions et ne peut utiliser que les membres publics de string.
Les règles de déclaration
- La méthode doit être
staticet vivre dans une classestatic, non générique et non imbriquée dans une autre classe. Une classe non statique ou générique donne CS1106,Extension method must be defined in a non-generic static class; une classe imbriquée donne CS1109,Extension methods must be defined in a top level static class. thisse place uniquement sur le premier paramètre, et le type de ce paramètre est le type étendu. Les paramètres suivants sont normaux.- La méthode peut elle-même être générique (
this IEnumerable<T> source), même si la classe ne le peut pas. - Elle ne voit que ce que voit tout code extérieur : les membres publics (et, dans le même assembly, internal). Les champs privés restent privés.
La convention est de nommer la classe d'après ce qu'elle étend (StringExtensions, EnumerableExtensions) et de les regrouper dans un namespace comme MyApp.Extensions.
Étendre des interfaces et IEnumerable<T>
Étendre une interface ajoute la méthode à chaque type qui l'implémente. Étendre IEnumerable<T> donne donc la méthode d'un coup aux tableaux, aux listes, aux ensembles, aux collections de clés des dictionnaires et aux résultats des requêtes LINQ :
Sortie :
9.5
50
65
f0 f3 f6
EveryNth est générique : T est déduit du récepteur, donc elle fonctionne ici pour des chaînes et pour tout autre type d'élément. Comme elle utilise yield return, elle est paresseuse comme les opérateurs LINQ intégrés et peut s'enchaîner avec eux.
LINQ est construit sur les méthodes d'extension
Where, Select, OrderBy, Sum, First et le reste de LINQ sont des méthodes d'extension sur IEnumerable<T>, définies dans la classe statique System.Linq.Enumerable. C'est pourquoi using System.Linq; doit figurer en haut d'un fichier pour que list.Where(...) compile, et pourquoi LINQ fonctionne sur tous les types de collection sans qu'aucun d'eux n'implémente de méthode Where :
Sortie :
72, 88, 95
72, 88, 95
Les deux lignes sont le même code. La syntaxe d'extension transforme des appels imbriqués qui se lisent de l'intérieur vers l'extérieur en une chaîne qui se lit dans l'ordre où les étapes ont lieu, et c'est la principale raison d'être des méthodes d'extension : elles ont été ajoutées en C# 3 en même temps que LINQ. La page LINQ couvre les opérateurs.
Le namespace doit être importé
Une méthode d'extension n'est accessible que lorsque le namespace de sa classe statique est importé avec using. Sans cela, l'appel échoue comme si la méthode n'existait pas :
error CS1061: 'string' does not contain a definition for 'Truncate' and no accessible extension method 'Truncate' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
Ajoutez le using du namespace des extensions (ou, depuis C# 10, un global using une fois pour tout le projet) :
Sortie :
EUR 49.90
Le revers est utile : une méthode d'extension que vous ne voulez pas partout peut vivre dans un namespace que seuls certains fichiers importent.
Les méthodes d'instance l'emportent
Quand un type a déjà une méthode d'instance applicable, le compilateur l'utilise et ne considère jamais l'extension. Une extension de même signature est du code mort à chaque appel qui utilise la syntaxe à point :
Sortie :
Invoice (instance method): 120
Invoice (extension): 120 USD
extension: only reachable as a static call
L'extension à un argument a perdu face à la méthode d'instance ; celle à deux arguments a été utilisée parce qu'aucune méthode d'instance ne prend de chaîne. Cela signifie aussi qu'une mise à jour de bibliothèque qui ajoute une méthode d'instance portant le nom de votre extension fait basculer silencieusement vos appels vers la version de la bibliothèque. Choisissez des noms d'extension peu susceptibles d'entrer en collision.
Appeler sur null
Comme l'appel est en réalité un appel statique, un récepteur null ne lève pas d'exception à l'endroit de l'appel. La méthode reçoit null comme premier argument et décide de ce que cela signifie :
Sortie :
True
AL
ArgumentNullException: fullName
Que missing.IsBlank() renvoie true est pratique, mais peut surprendre le lecteur qui s'attend à ce qu'un appel de membre sur null lève une exception. N'acceptez null que dans des méthodes dont le nom le rend évident (IsBlank, OrEmpty) ; ailleurs, levez ArgumentNullException comme le fait LINQ.
Étendre des enums
Les enums ne peuvent pas avoir de méthodes propres, ce qui en fait une cible naturelle :
enum OrderStatus { Pending, Paid, Shipped, Delivered, Cancelled }
static class OrderStatusExtensions
{
public static bool IsFinal(this OrderStatus s) =>
s == OrderStatus.Delivered || s == OrderStatus.Cancelled;
}
// usage: if (order.Status.IsFinal()) { ... }
Les membres d'extension de C# 14
Jusqu'à C# 14, seules les méthodes pouvaient être des extensions. C# 14 a ajouté les blocs extension, qui regroupent les membres pour un récepteur et autorisent aussi les propriétés d'extension et les membres statiques :
// C# 14 and later
public static class StringExtensions
{
extension(string s)
{
public bool IsBlank => string.IsNullOrWhiteSpace(s); // extension property
public string Truncate(int max) => s.Length <= max ? s : s[..max] + "...";
}
}
Les méthodes d'extension classiques à paramètre this restent valides, et c'est ce qu'utilisent presque tout le code et toutes les bibliothèques existants.
Quand en écrire une
Les méthodes d'extension conviennent quand le type ne vous appartient pas (string, DateTime, les interfaces du framework), quand vous voulez la même fonction sur toutes les implémentations d'une interface, ou quand une chaîne se lit mieux que des appels imbriqués. Quand la classe vous appartient, ajoutez plutôt une vraie méthode d'instance : elle peut utiliser l'état privé et apparaît là où les lecteurs la cherchent. Et évitez d'étendre object, ce qui placerait votre méthode dans la liste de complétion de chaque valeur du programme.
Questions fréquentes
Qu'est-ce qu'une méthode d'extension en C# ?
Une méthode statique qu'on peut appeler comme s'il s'agissait d'une méthode d'instance d'un autre type. Vous l'écrivez dans une classe statique et placez this devant son premier paramètre : public static bool IsBlank(this string s) => string.IsNullOrWhiteSpace(s);. Ensuite, name.IsBlank() fonctionne sur n'importe quelle chaîne, sans modifier string ni en hériter.
Pourquoi ma méthode d'extension est-elle introuvable ?
Les méthodes d'extension ne sont visibles que lorsque leur namespace est importé. Si la classe statique est dans MyApp.Extensions, ajoutez using MyApp.Extensions; au fichier qui l'appelle ; sinon le compilateur signale CS1061, indiquant que le type ne contient aucune définition ni méthode d'extension accessible de ce nom. Vérifiez aussi que la classe est static, non générique et non imbriquée.
Une méthode d'extension peut-elle remplacer une méthode d'instance ?
Non. Le compilateur cherche d'abord une méthode d'instance applicable et ne considère les méthodes d'extension que s'il n'en existe aucune. Une extension portant le même nom et les mêmes paramètres qu'une méthode d'instance est silencieusement ignorée à chaque appel qui utilise la syntaxe d'instance.
Peut-on appeler une méthode d'extension sur null en C# ?
Oui. L'appel se compile en un appel de méthode statique avec null comme premier argument, donc aucune NullReferenceException n'est levée à l'appel lui-même. La méthode décide quoi faire : traiter null comme une entrée valide (comme une fonction à la manière de IsNullOrEmpty) ou lever ArgumentNullException, comme le font les méthodes de LINQ.
Existe-t-il des propriétés d'extension en C# ?
Pas avant C# 14. Jusque-là, seules les méthodes peuvent être des extensions, donc une propriété potentielle s'écrit comme une méthode (GetFullName()). C# 14 a ajouté les blocs d'extension, qui peuvent déclarer des propriétés d'extension et des membres statiques en plus des méthodes.