Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Les membres d’extension vous permettent d’ajouter des méthodes à des types existants sans créer de nouveau type dérivé, recompiler ou modifier le type d’origine.
À compter de C# 14, il existe deux syntaxes que vous utilisez pour définir des méthodes d’extension. C# 14 ajoute extension
des conteneurs, où vous définissez plusieurs membres d’extension pour un type ou une instance d’un type. Avant C# 14, vous ajoutez le this
modificateur au premier paramètre d’une méthode statique pour indiquer que la méthode apparaît en tant que membre d’une instance du type de paramètre.
Les méthodes d’extension sont des méthodes statiques, mais elles sont appelées comme si elles étaient des méthodes d’instance sur le type étendu. Pour le code client écrit en C#, F# et Visual Basic, il n’existe aucune différence apparente entre l’appel d’une méthode d’extension et les méthodes définies dans un type. Les deux formes de méthodes d’extension sont compilées sur le même il (langage intermédiaire). Les utilisateurs des membres d’extension n’ont pas besoin de savoir quelle syntaxe a été utilisée pour définir les méthodes d’extension.
Les membres d’extension les plus courants sont les opérateurs de requête standard LINQ qui fournissent des fonctionnalités de requête aux types existants System.Collections.IEnumerable et System.Collections.Generic.IEnumerable<T>. Pour utiliser les opérateurs de requête standard, d'abord introduisez-les dans la portée avec une using System.Linq
directive. Ensuite, tout type qui implémente IEnumerable<T> semble avoir des méthodes d’instance telles que GroupBy, OrderBy, Average, et ainsi de suite. Vous pouvez voir ces méthodes supplémentaires dans la complétion d'instruction IntelliSense lorsque vous tapez « point » après une instance d'un type IEnumerable<T> tel que List<T> ou Array.
Exemple de tri par (OrderBy)
L’exemple suivant montre comment appeler la méthode d’opérateur OrderBy
de requête standard sur un tableau d’entiers. L’expression entre parenthèses est une expression lambda. De nombreux opérateurs de requête standard prennent des expressions lambda en tant que paramètres. Pour plus d’informations, consultez Expressions lambda.
int[] numbers = [10, 45, 15, 39, 21, 26];
IOrderedEnumerable<int> result = numbers.OrderBy(g => g);
foreach (int i in result)
{
Console.Write(i + " ");
}
//Output: 10 15 21 26 39 45
Les méthodes d’extension sont définies comme des méthodes statiques, mais sont appelées à l’aide de la syntaxe de méthode d’instance. Leur premier paramètre spécifie le type sur lequel la méthode fonctionne. Le paramètre suit ce modificateur. Les méthodes d’extension ne sont disponibles que lorsque vous importez explicitement l’espace de noms dans votre code source avec une directive using
.
Déclarer des membres d’extension
À compter de C# 14, vous pouvez déclarer des blocs d’extension. Un bloc d’extension est un bloc dans une classe statique non imbriquée, non générique, qui contient des membres d’extension pour un type ou une instance de ce type. L’exemple de code suivant définit un bloc d’extension pour le string
type. Le bloc d’extension contient un membre : une méthode qui compte les mots dans la chaîne :
namespace CustomExtensionMembers;
public static class MyExtensions
{
extension(string str)
{
public int WordCount() =>
str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
}
}
Avant C# 14, vous déclarez une méthode d’extension en ajoutant le this
modificateur au premier paramètre :
namespace CustomExtensionMethods;
public static class MyExtensions
{
public static int WordCount(this string str) =>
str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
}
Les deux formes d’extensions doivent être définies à l’intérieur d’une classe statique non imbriquée et non générique.
Il peut également être appelé à partir d’une application à l’aide de la syntaxe permettant d’accéder aux membres d’instance :
string s = "Hello Extension Methods";
int i = s.WordCount();
Bien que les membres de l'extension ajoutent de nouvelles fonctionnalités à un type existant, les membres d'extension ne violent pas le principe d'encapsulation. Les déclarations d’accès pour tous les membres du type étendu s’appliquent aux membres de l’extension.
La MyExtensions
classe et la WordCount
méthode sont static
, et elles sont accessibles comme tous les autres membres static
. La WordCount
méthode peut être appelée comme d’autres static
méthodes comme suit :
string s = "Hello Extension Methods";
int i = MyExtensions.WordCount(s);
Le code C# précédent s’applique à la fois au bloc d’extension et à la syntaxe des membres de l’extension, représentée par this
. Code précédent :
- Déclare et assigne un nouveau
string
noms
avec une valeur de"Hello Extension Methods"
. - Appelle
MyExtensions.WordCount
avec l'arguments
donné.
Pour plus d’informations, consultez Comment implémenter et appeler une méthode d’extension personnalisée.
En général, vous appelez probablement des membres d’extension bien plus souvent que vous ne les implémentez. Étant donné que les membres d’extension sont appelés comme s’ils sont déclarés en tant que membres de la classe étendue, aucune connaissance spéciale n’est requise pour les utiliser à partir du code client. Pour activer les membres d’extension pour un type particulier, ajoutez simplement une using
directive pour l’espace de noms dans lequel les méthodes sont définies. Par exemple, pour utiliser les opérateurs de requête standard, ajoutez cette using
directive à votre code :
using System.Linq;
Liaison de membres d’extension au moment de la compilation
Vous pouvez utiliser des membres d’extension pour étendre une classe ou une interface, mais pas pour remplacer le comportement défini dans une classe. Un membre d’extension portant le même nom et la même signature qu’une interface ou des membres de classe ne sont jamais appelés. Au moment de la compilation, les membres d’extension ont toujours une priorité inférieure aux membres d’instance (ou statiques) définis dans le type lui-même. En d’autres termes, si un type a une méthode nommée Process(int i)
, et que vous disposez d’une méthode d’extension avec la même signature, le compilateur est toujours lié à la méthode membre. Lorsque le compilateur rencontre une invocation d'un membre, il recherche d’abord une correspondance dans les membres du type. Si aucune correspondance n'est trouvée, le système recherche tout membre d'extension défini pour le type. Il se lie au premier membre d’extension qu’il trouve. L’exemple suivant illustre les règles que le compilateur C# suit pour déterminer s’il faut établir une liaison à un membre d’instance sur le type ou à un membre d’extension. La classe Extensions
statique contient des membres d’extension définis pour tout type implémentant IMyInterface
:
public interface IMyInterface
{
void MethodB();
}
// Define extension methods for IMyInterface.
// The following extension methods can be accessed by instances of any
// class that implements IMyInterface.
public static class Extension
{
public static void MethodA(this IMyInterface myInterface, int i) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
public static void MethodA(this IMyInterface myInterface, string s) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
// This method is never called in ExtensionMethodsDemo1, because each
// of the three classes A, B, and C implements a method named MethodB
// that has a matching signature.
public static void MethodB(this IMyInterface myInterface) =>
Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
}
Les extensions équivalentes peuvent être déclarées à l’aide de la syntaxe de membre d’extension C# 14 :
public static class Extension
{
extension(IMyInterface myInterface)
{
public void MethodA(int i) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
public void MethodA(string s) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
// This method is never called in ExtensionMethodsDemo1, because each
// of the three classes A, B, and C implements a method named MethodB
// that has a matching signature.
public void MethodB() =>
Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
}
}
Classes A
, B
et C
tous implémentent l’interface :
// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
class A : IMyInterface
{
public void MethodB() { Console.WriteLine("A.MethodB()"); }
}
class B : IMyInterface
{
public void MethodB() { Console.WriteLine("B.MethodB()"); }
public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
}
class C : IMyInterface
{
public void MethodB() { Console.WriteLine("C.MethodB()"); }
public void MethodA(object obj)
{
Console.WriteLine("C.MethodA(object obj)");
}
}
La MethodB
méthode d’extension n’est jamais appelée, car son nom et sa signature correspondent exactement aux méthodes déjà implémentées par les classes. Lorsque le compilateur ne trouve pas de méthode d’instance avec une signature correspondante, il se lie à une méthode d’extension correspondante s’il en existe une.
// Declare an instance of class A, class B, and class C.
A a = new A();
B b = new B();
C c = new C();
// For a, b, and c, call the following methods:
// -- MethodA with an int argument
// -- MethodA with a string argument
// -- MethodB with no argument.
// A contains no MethodA, so each call to MethodA resolves to
// the extension method that has a matching signature.
a.MethodA(1); // Extension.MethodA(IMyInterface, int)
a.MethodA("hello"); // Extension.MethodA(IMyInterface, string)
// A has a method that matches the signature of the following call
// to MethodB.
a.MethodB(); // A.MethodB()
// B has methods that match the signatures of the following
// method calls.
b.MethodA(1); // B.MethodA(int)
b.MethodB(); // B.MethodB()
// B has no matching method for the following call, but
// class Extension does.
b.MethodA("hello"); // Extension.MethodA(IMyInterface, string)
// C contains an instance method that matches each of the following
// method calls.
c.MethodA(1); // C.MethodA(object)
c.MethodA("hello"); // C.MethodA(object)
c.MethodB(); // C.MethodB()
/* Output:
Extension.MethodA(this IMyInterface myInterface, int i)
Extension.MethodA(this IMyInterface myInterface, string s)
A.MethodB()
B.MethodA(int i)
B.MethodB()
Extension.MethodA(this IMyInterface myInterface, string s)
C.MethodA(object obj)
C.MethodA(object obj)
C.MethodB()
*/
Modèles d’utilisation courants
Fonctionnalité de collecte
Dans le passé, il était courant de créer des « classes de collection » qui implémentaient l’interface System.Collections.Generic.IEnumerable<T> pour un type donné et contenaient des fonctionnalités qui agissaient sur des collections de ce type. Bien qu’il n’y ait rien de mal à créer ce type d’objet de collection, la même fonctionnalité peut être obtenue à l’aide d’une extension sur le System.Collections.Generic.IEnumerable<T>. Les extensions ont l’avantage de permettre à la fonctionnalité d’être appelée à partir de n’importe quelle collection telle qu’une System.Array ou System.Collections.Generic.List<T> qui implémente System.Collections.Generic.IEnumerable<T> sur ce type. Vous trouverez un exemple de ceci à l’aide d’un tableau d’Int32 plus haut dans cet article.
Fonctionnalités de Layer-Specific
Lorsque vous utilisez une architecture Onion ou une autre conception d’application en couches, il est courant d’avoir un ensemble d’entités de domaine ou d’objets de transfert de données qui peuvent être utilisés pour communiquer entre les limites de l’application. Ces objets ne contiennent généralement aucune fonctionnalité, ou uniquement des fonctionnalités minimales qui s’appliquent à toutes les couches de l’application. Les méthodes d’extension peuvent être utilisées pour ajouter des fonctionnalités spécifiques à chaque couche d’application.
public class DomainEntity
{
public int Id { get; set; }
public required string FirstName { get; set; }
public required string LastName { get; set; }
}
static class DomainEntityExtensions
{
static string FullName(this DomainEntity value)
=> $"{value.FirstName} {value.LastName}";
}
Vous pouvez déclarer une propriété équivalente FullName
en C# 14 et versions ultérieures à l’aide de la nouvelle syntaxe de bloc d’extension :
static class DomainEntityExtensions
{
extension(DomainEntity value)
{
string FullName => $"{value.FirstName} {value.LastName}";
}
}
Extension de types prédéfinis
Au lieu de créer de nouveaux objets lorsque des fonctionnalités réutilisables doivent être créées, vous pouvez souvent étendre un type existant, tel qu’un type .NET ou CLR. Par exemple, si vous n’utilisez pas de méthodes d’extension, vous pouvez créer une classe Engine
ou Query
pour effectuer le travail d’exécution d’une requête sur un serveur SQL Server, qui peut être appelée à partir de plusieurs emplacements dans notre code. Toutefois, vous pouvez étendre la classe à l’aide System.Data.SqlClient.SqlConnection de méthodes d’extension pour effectuer cette requête à partir de n’importe quel endroit où vous disposez d’une connexion à un serveur SQL Server. D’autres exemples peuvent être d’ajouter des fonctionnalités courantes à la System.String classe, d’étendre les fonctionnalités de traitement des données de l’objet System.IO.Stream et System.Exception d’objets pour des fonctionnalités de gestion des erreurs spécifiques. Ces types de cas d’usage sont limités uniquement par votre imagination et votre bon sens.
L'extension de types prédéfinis peut être difficile avec les types struct
, car ils sont passés par valeur aux méthodes. Cela signifie que les modifications apportées à la structure de données sont effectuées sur une copie de cette structure. Ces modifications ne sont pas visibles une fois la méthode d’extension terminée. Vous pouvez ajouter le ref
modificateur au premier argument qui en fait une méthode d’extension ref
. Le ref
mot clé peut apparaître avant ou après le this
mot clé sans aucune différence sémantique. L’ajout du ref
modificateur indique que le premier argument est passé par référence. Cette technique vous permet d’écrire des méthodes d’extension qui modifient l’état du struct en cours d’extension (notez que les membres privés ne sont pas accessibles). Seuls les types valeur ou les types génériques contraints à struct (pour plus d’informations sur ces règles, consultez struct
la contrainte pour plus d’informations) sont autorisés comme premier paramètre d’une méthode d’extension ref
ou en tant que récepteur d’un bloc d’extension. L’exemple suivant montre comment utiliser une méthode d’extension ref
pour modifier directement un type intégré sans avoir à réaffecter le résultat ou à le transmettre à une fonction avec le ref
mot clé :
public static class IntExtensions
{
public static void Increment(this int number)
=> number++;
// Take note of the extra ref keyword here
public static void RefIncrement(this ref int number)
=> number++;
}
Les blocs d’extension équivalents sont affichés dans le code suivant :
public static class IntExtensions
{
extension(int number)
{
public void Increment()
=> number++;
}
// Take note of the extra ref keyword here
extension(ref int number)
{
public void RefIncrement()
=> number++;
}
}
Différents blocs d’extension sont nécessaires pour distinguer les modes de paramètres by value et by ref pour le récepteur.
Vous pouvez constater la différence qu'apporte l'application de ref
au récepteur dans l'exemple suivant :
int x = 1;
// Takes x by value leading to the extension method
// Increment modifying its own copy, leaving x unchanged
x.Increment();
Console.WriteLine($"x is now {x}"); // x is now 1
// Takes x by reference leading to the extension method
// RefIncrement changing the value of x directly
x.RefIncrement();
Console.WriteLine($"x is now {x}"); // x is now 2
Vous pouvez appliquer la même technique en ajoutant ref
des membres d’extension aux types de struct définis par l’utilisateur :
public struct Account
{
public uint id;
public float balance;
private int secret;
}
public static class AccountExtensions
{
// ref keyword can also appear before the this keyword
public static void Deposit(ref this Account account, float amount)
{
account.balance += amount;
// The following line results in an error as an extension
// method is not allowed to access private members
// account.secret = 1; // CS0122
}
}
L’exemple précédent peut également être créé à l’aide de blocs d’extension en C# 14 :
public static class AccountExtensions
{
extension(ref Account account)
{
// ref keyword can also appear before the this keyword
public void Deposit(float amount)
{
account.balance += amount;
// The following line results in an error as an extension
// method is not allowed to access private members
// account.secret = 1; // CS0122
}
}
}
Vous pouvez accéder à ces méthodes d’extension comme suit :
Account account = new()
{
id = 1,
balance = 100f
};
Console.WriteLine($"I have ${account.balance}"); // I have $100
account.Deposit(50f);
Console.WriteLine($"I have ${account.balance}"); // I have $150
Instructions générales
Il est préférable d’ajouter des fonctionnalités en modifiant le code d’un objet ou en dérivant un nouveau type chaque fois qu’il est raisonnable et possible de le faire. Les méthodes d’extension constituent une option cruciale pour créer des fonctionnalités réutilisables dans l’écosystème .NET. Les membres d’extension sont préférables lorsque la source d’origine n’est pas sous votre contrôle, lorsqu’un objet dérivé est inapproprié ou impossible, ou lorsque la fonctionnalité a une étendue limitée.
Pour plus d’informations sur les types dérivés, consultez Héritage.
Si vous implémentez des méthodes d’extension pour un type donné, n’oubliez pas les points suivants :
- Une méthode d’extension n’est pas appelée si elle a la même signature qu’une méthode définie dans le type.
- Les méthodes d’extension sont introduites dans l’étendue au niveau de l’espace de noms. Par exemple, si vous avez plusieurs classes statiques qui contiennent des méthodes d’extension dans un espace de noms unique nommé
Extensions
, toutes sont introduites dans l’étendue par lausing Extensions;
directive.
Pour une bibliothèque de classes que vous avez implémentée, vous ne devez pas utiliser de méthodes d’extension pour éviter d’incrémenter le numéro de version d’un assembly. Si vous souhaitez ajouter des fonctionnalités significatives à une bibliothèque pour laquelle vous êtes propriétaire du code source, suivez les instructions .NET pour le contrôle de version d’assembly. Pour plus d’informations, consultez Le contrôle de version d’assembly.
Voir aussi
- Exemples de programmation parallèle (de nombreux exemples illustrent les méthodes d’extension)
- Lambda Expressions
- Vue d’ensemble des opérateurs de requête standard
- Règles de conversion pour les paramètres d’instance et leur impact
- Interopérabilité des méthodes d’extension entre les langages
- Méthodes d’extension et délégués curried
- Méthodes d’extension et rapport d’erreurs