Les méthodes d’extension sont une partie fondamentale de C# et de la programmation orientée objet (POO). Les méthodes d’extension en C# vous permettent « d’étendre » des types existants, y compris des classes, des interfaces ou des structures, sans modifier leur code original.

Ceci est particulièrement utile lorsque vous souhaitez ajouter de nouvelles fonctionnalités à un type que vous ne possédez pas ou que vous ne pouvez pas modifier, comme les types provenant de bibliothèques tierces ou les types intégrés de .NET tels que string, List<T>, et ainsi de suite.

Dans cet article, vous apprendrez comment ajouter des méthodes d’extension à vos classes, ainsi qu’aux classes tierces et système.

Table des matières

Comment créer des méthodes d’extension DateTime

Disons que nous voulons des méthodes qui peuvent être utilisées avec la classe DateTime existante, peut-être une méthode qui retourne si l’objet DateTime donné est un week-end ou autre chose.

Les méthodes d’extension doivent être définies dans une classe statique car elles sont essentiellement du sucre syntaxique qui vous permet d’appeler une méthode statique comme s’il s’agissait d’une méthode d’instance sur le type que vous étendez.

Les méthodes d’extension doivent être dans une classe statique car:

  1. Pas d’objet nécessaire: Vous n’avez pas besoin de créer un objet pour utiliser une méthode d’extension. Puisque la méthode ajoute une nouvelle fonctionnalité à un type existant (comme string), elle peut fonctionner sans avoir besoin d’une instance de la classe.

  2. Code organisé: Placer les méthodes d’extension dans une classe statique permet de garder les choses en ordre. Cela vous permet de regrouper les méthodes liées, et vous pouvez facilement les inclure dans votre code en utilisant l’espace de noms approprié.

Donc, en utilisant une classe statique, vous pouvez ajouter des méthodes utiles à des types existants sans modifier leur code d’origine, et vous n’avez pas besoin d’un objet pour les appeler.

Commencez par créer une classe statique DateTimeExtensions.

public static class DateTimeExtensions {

}

Cela regroupera toutes les extensions DateTime que nous voulons créer.

public static bool IsWeekend(this DateTime date)
{
    return date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;
}

Explication:

public static bool IsWeekend: Cela définit qu’il s’agit d’une méthode statique appelée IsWeekend qui renverra une valeur bool (vrai/faux).

cette DateTime date : Le mot-clé this en tant qu’argument de méthode indique que cette méthode est une méthode d’extension. Cela signifie que la méthode sera une extension de la classe DateTime.

Comment chaîner des méthodes d’extension de même type

Pour qu’une méthode d’extension puisse être chaînée avec d’autres, elle doit généralement retourner le même type que celui qu’elle étend (ou un type compatible). Cela permet d’appeler une autre méthode sur le résultat de la précédente.

using System.Globalization;

public static string ToTitleCase(this string str)
{
    return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str.ToLower());
}

public static string TrimAndAppend(this string str, string toAppend)
{
    return str.Trim() + toAppend;
}

Dans l’exemple ci-dessus, les méthodes ToTitleCase et TrimAndAppend retournent une valeur de type chaîne, ce qui signifie que nous pouvons chaîner les méthodes d’extension comme ci-dessous, ce qui va convertir la chaîne en casse de titre avant de supprimer tous les espaces et d’ajouter la chaîne fournie.

Remarquez que nous avons seulement fourni le deuxième paramètre à la méthode TrimAndAppend, car le premier paramètre est la chaîne à laquelle la méthode d’extension est appliquée (comme expliqué précédemment, indiqué par le mot-clé this).

var title = "hello world   "
    .ToTitleCase()
    .TrimAndAppend("!!");

//Sortie :
// Bonjour le monde !!

Si la méthode d’extension retourne un type différent (ni le type original ni un type compatible), vous ne pouvez pas la chaîner. Par exemple :

var date = new DateTime();
date.IsWeekend().AddDays(1);

Pour des raisons moins évidentes, cela ne fonctionnera pas. Lorsque vous chaînez des méthodes, elles ne se chaînent pas à partir de la variable originale, mais à partir du type de retour de l’appel de méthode précédent.

Ici, nous avons une date appelée IsWeekend() qui renvoie un booléen. Nous avons ensuite tenté d’appeler AddDays(1) sur une valeur booléenne qui n’existe pas, car c’est une extension DateTime. Le compilateur de code échouera à construire, soulevant une erreur vous informant de cela.

Comment retourner l’instance pour chaîner

Dans certaines méthodes d’extension, en particulier celles pour la configuration (comme l’injection de dépendance), vous retournez la même instance pour permettre le chaînage de méthodes. Cela vous permet de continuer à travailler avec l’objet d’origine ou son état modifié à travers plusieurs appels, permettant une interface fluide.

Prenons l’exemple d’une liste de voitures.

public static List<T> RemoveDuplicates<T>(this List<T> list)
{
    // Utilisez Distinct pour supprimer les doublons et mettre à jour la liste
    list = list.Distinct().ToList();

    // Retournez la liste modifiée pour permettre le chaînage de méthodes
    return list;
}

public static List<T> AddRangeOfItems<T>(this List<T> list, IEnumerable<T> items)
{
    // Ajoutez une plage d'éléments à la liste
    list.AddRange(items);

    // Retournez la liste modifiée pour permettre le chaînage de méthodes
    return list;  
}

Maintenant que nous avons retourné la liste de ces méthodes d’extension, nous pouvons chaîner d’autres méthodes sur la même liste. Par exemple, après avoir supprimé les doublons avec RemoveDuplicates(), nous pouvons immédiatement appeler AddRangeOfItems() sur la même liste.

Nous pouvons donc faire quelque chose comme :

var existingStock = new List<string> { "Ford", "Jaguar", "Ferrari", "Ford", "Renault" };

var availableBrands = existingStock
    .RemoveDuplicates()
    .AddRangeOfItems(new[] { "Lamborghini" }); // nouveau stock disponible

Console.WriteLine("Brands Available Now: " + string.Join(", ", availableBrands));

// Sortie : Marques disponibles maintenant : Ford, Jaguar, Ferrari, Renault, Lamborghini

Nous avons supprimé les doublons d’une liste de marques de voitures et ajouté un nouvel stock à la même liste. Cela fonctionne car RemoveDuplicates renvoie la liste, nous permettant de la chaîner avec AddRangeOfItems.

Si RemoveDuplicates renvoyait void au lieu de la liste, nous ne pourrions pas enchaîner les méthodes. Il supprimerait toujours les doublons, mais d’autres actions comme l’ajout de nouveaux stocks ne seraient pas possibles dans la même expression.

Nous devrions également mettre à jour le RemoveDuplicates pour mettre à jour l’argument de liste passé, car Distinct() renvoie une nouvelle liste qui n’est pas renvoyée comme indiqué ci-dessous, ce qui, je pense, vous conviendrez, est beaucoup plus verbeux.

public static void RemoveDuplicates<T>(this List<T> list)
{
    // Obtenir les éléments distincts et vider la liste d'origine
    var distinctItems = list.Distinct().ToList();
    list.Clear(); 

    // Ajouter à nouveau les éléments distincts à la liste d'origine
    list.AddRange(distinctItems);
}

Pourquoi ne puis-je pas simplement ajouter ces méthodes à ma classe ?

Si la méthode n’est pas une partie essentielle de la fonctionnalité de la classe, la placer dans une méthode d’extension peut aider à maintenir la classe concentrée et maintenable.

Séparation des préoccupations: L’utilisation de méthodes d’extension rend votre code plus propre et aide à réduire la complexité. Cela permet d’éviter de surcharger la classe avec des méthodes qui ne sont peut-être pas utilisées fréquemment.

Amélioration des bibliothèques externes: Si vous utilisez une bibliothèque ou un framework où vous ne pouvez pas modifier le code source, les méthodes d’extension vous permettent d’ajouter des fonctionnalités à ces types sans modifier leurs définitions.

Disons que vous utilisez la classe FileInfo de l’espace de noms System.IO pour travailler avec des fichiers. Vous souhaiterez peut-être ajouter une méthode pour vérifier facilement si un fichier est trop volumineux (par exemple, plus de 1 Go), mais vous ne pouvez pas modifier directement la classe FileInfo car elle appartient à l’espace de noms System.IO (c’est-à-dire qu’elle est intégrée dans .Net).

Sans une extension:

var fileInfo = new FileInfo("myFile.txt");

if (fileInfo.Length > 1024 * 1024 * 1024) // la taille du fichier est supérieure à 1 Go
{
    Console.WriteLine("The file is too large.");
}
else
{
    Console.WriteLine("The file size is acceptable.");
}

Avec une méthode d’extension:

Vous pouvez rendre cela plus réutilisable en ajoutant une méthode d’extension qui vérifie si le fichier est plus grand que 1 Go.

public static class FileInfoExtensions
{
    // méthode d'extension, avec une taille de fichier par défaut de 1 Go (pouvant être remplacée)
    public static bool IsFileTooLarge(this FileInfo fileInfo, long sizeInBytes = 1024 * 1024 * 1024)
    {
        return fileInfo.Length > sizeInBytes;
    }
}

Vous pouvez maintenant utiliser la méthode IsFileTooLarge directement sur les objets FileInfo, rendant votre code plus propre:

csharpCopy codevar fileInfo = new FileInfo("myFile.txt");

if (fileInfo.IsFileTooLarge())
{
    Console.WriteLine("The file is too large.");
}
else
{
    Console.WriteLine("The file size is acceptable.");
}

Étendre les bibliothèques et packages tiers peut rendre votre code beaucoup plus compatible.

Meilleure organisation et lisibilité: Vous pouvez organiser les méthodes d’extension dans des classes statiques en fonction de la fonctionnalité ou du contexte, ce qui les rend plus faciles à trouver et à utiliser. Cela est certainement amélioré en permettant aux méthodes d’extension d’être chaînées.

Quand utiliser les extensions

  • Pour les méthodes utilitaires: Si vous avez des méthodes utilitaires utiles pour un type mais qui ne doivent pas appartenir directement au type lui-même (par exemple, le formatage, la validation).

  • Pour améliorer les types intégrés : Si vous souhaitez ajouter des fonctionnalités aux types intégrés (comme string ou DateTime) sans les modifier.

  • Lorsque vous souhaitez garder les méthodes optionnelles : Si vous voulez fournir des méthodes supplémentaires que les utilisateurs peuvent choisir d’utiliser sans les forcer à les incorporer dans la conception principale de la classe.

Scénario d’exemple

Imaginez que vous avez une classe Person, et vous souhaitez ajouter une méthode pour formater joliment le nom de la personne :

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

// Méthode d'extension dans une classe statique
public static class PersonExtensions
{
    public static string GetFullName(this Person person)
    {
        return $"{person.FirstName} {person.LastName}";
    }
}

En utilisant une méthode d’extension pour GetFullName, vous pouvez garder la classe Person simple et concentrée sur ses responsabilités principales, tout en fournissant des fonctionnalités utiles.

Quand ne pas utiliser les méthodes d’extension

  • Pour la fonctionnalité principale : Si une méthode est essentielle au comportement principal d’une classe, elle doit faire partie de la classe elle-même, et non d’une extension.

  • Pour un couplage serré: Si la méthode d’extension nécessite une connaissance intime de l’état privé de la classe ou nécessite un accès régulier à sa logique interne.

  • Pour les API publiques: Lors de la conception d’une bibliothèque ou d’une API destinée au public, il est souvent préférable d’inclure les méthodes nécessaires directement dans la classe plutôt que de forcer les utilisateurs à trouver ou créer leurs propres méthodes d’extension.

Points à prendre en compte lors de la conception d’extensions

Bien que les méthodes d’extension soient puissantes et pratiques dans de nombreux cas, il existe certains inconvénients ou situations où les utiliser pourrait ne pas être le meilleur choix:

Comportement caché/confusion

  • Les méthodes d’extension n’apparaissent pas directement dans la définition de la classe, ce qui signifie qu’elles peuvent être plus difficiles à découvrir pour les développeurs qui ne sont pas familiers avec les extensions disponibles.

  • Les développeurs doivent savoir que ces méthodes d’extension existent, sinon ils risquent de ne pas les utiliser à moins qu’ils ne travaillent dans un IDE avec des fonctionnalités comme IntelliSense (par exemple, Visual Studio, JetBrains Rider). Ces IDE peuvent suggérer des méthodes d’extension provenant d’autres fichiers ou espaces de noms lorsqu’ils détectent le type approprié. Sans un IDE riche en fonctionnalités, le développeur doit être conscient des méthodes d’extension ou trouver le dossier dans lequel elles sont stockées.

Impossible d’accéder aux membres privés

  • Les méthodes d’extension ne peuvent accéder qu’aux membres (méthodes, propriétés, champs) qui sont publics ou internes.

  • Elles ne peuvent pas accéder aux membres privés ou protégés d’une classe car les méthodes d’extension fonctionnent comme si elles faisaient partie de la classe de l’extérieur, semblable aux appels de méthodes réguliers depuis l’extérieur de la classe.

Exemple :

public class Car
{
    private string engineNumber = "12345"; // Champ privé

    public string Brand { get; set; } = "Ford"; // Propriété publique

    private void StartEngine() // Méthode privée
    {
        Console.WriteLine("Engine started");
    }
}
public static class CarExtensions
{
    public static void DisplayBrand(this Car car)
    {
        Console.WriteLine($"Brand: {car.Brand}"); // Accéder à la propriété publique 'Marque'
    }

    public static void TryAccessPrivateField(this Car car)
    {
        // Impossible d'accéder à 'engineNumber' privé
        // Cela entraînera une erreur de compilation.
        Console.WriteLine(car.engineNumber);
    }
}

Duplication et surutilisation de code

  • Dans certains cas, les méthodes d’extension peuvent encourager la duplication de code. Si plusieurs projets ou classes nécessitent des méthodes d’extension similaires, vous pourriez finir par écrire ou copier les mêmes méthodes d’extension à différents endroits, ce qui rend plus difficile la gestion et la mise à jour cohérente du code.

    Pour éviter cela, organisez efficacement votre code. Je recommanderais de garder toutes les extensions dans un dossier ou un projet d’extensions, proche de l’origine (selon les modèles de conception utilisés dans votre application).

  • Abus des extensions : Si utilisées de manière excessive, elles peuvent encombrer l’espace global avec des méthodes qui pourraient ne pas avoir besoin d’être globales. Cela peut entraîner une pollution de l’API du type, rendant plus difficile la compréhension de ce qui est essentiel à la classe par rapport à ce qui est ajouté via des extensions.

Dans certains cas, il est préférable d’encapsuler la fonctionnalité dans des classes d’assistance ou des services séparés plutôt que de l’ajouter via des méthodes d’extension.

Conclusion

Les méthodes d’extension sont utiles pour ajouter des fonctionnalités de manière propre et modulaire, mais elles peuvent également introduire de la confusion, des conflits de noms et un manque d’accès aux membres privés.

Comme souligné tout au long de l’article, elles ont de nombreuses utilisations et sont certainement une très belle fonctionnalité du framework Dotnet lorsqu’elles sont utilisées efficacement. Elles doivent être utilisées lorsque cela est approprié, mais non comme un substitut à la fonctionnalité qui appartient à la classe elle-même.