web-dev-qa-db-fra.com

Quels avantages des méthodes d'extension avez-vous trouvés?

Un "non-croyant" de C # me demandait quel était le but des méthodes d'extension. J'ai expliqué que vous pouviez ensuite ajouter de nouvelles méthodes à des objets déjà définis, en particulier lorsque vous ne possédez pas/ne contrôlez pas la source de l'objet d'origine.

Il a évoqué "Pourquoi ne pas simplement ajouter une méthode à votre propre classe?" Nous avons tourné en rond (dans le bon sens). Ma réponse générale est que c'est un autre outil dans la ceinture à outils, et sa réponse est que c'est un gaspillage inutile d'un outil ... mais je pensais que j'obtiendrais une réponse plus "éclairée".

Dans quels scénarios avez-vous utilisé des méthodes d'extension que vous n'auriez pas pu (ou n'auriez pas dû) utiliser une méthode ajoutée à votre propre classe?

83
Jerry

Je pense que les méthodes d'extension aident beaucoup lors de l'écriture de code, si vous ajoutez des méthodes d'extension aux types de base, vous les obtiendrez rapidement dans l'intellisense.

J'ai un fournisseur de format pour formater une taille de fichier . Pour l'utiliser, j'ai besoin d'écrire:

Console.WriteLine(String.Format(new FileSizeFormatProvider(), "{0:fs}", fileSize));

Création d'une méthode d'extension que je peux écrire:

Console.WriteLine(fileSize.ToFileSize());

Plus propre et plus simple.

33
Eduardo Campañó

L'avantage niquement des méthodes d'extension est la lisibilité du code. C'est ça.

Les méthodes d'extension vous permettent de faire ceci:

foo.bar();

au lieu de cela:

Util.bar(foo);

Maintenant, il y a beaucoup de choses en C # qui sont comme ça. En d'autres termes, il existe de nombreuses fonctionnalités en C # qui semblent triviales et n'ont pas de grands avantages en soi. Cependant, une fois que vous commencez à combiner ces fonctionnalités, vous commencez à voir quelque chose d'un peu plus grand que la somme de ses parties. LINQ bénéficie grandement des méthodes d'extension car les requêtes LINQ seraient presque illisibles sans elles. LINQ serait possible sans méthodes d'extension, mais pas pratique.

Les méthodes d'extension ressemblent beaucoup aux classes partielles de C #. En soi, ils ne sont pas très utiles et semblent triviaux. Mais lorsque vous commencez à travailler avec une classe qui a besoin de code généré, les classes partielles commencent à avoir beaucoup plus de sens.

83
Andrew Hare

N'oubliez pas l'outillage! Lorsque vous ajoutez une méthode d'extension M sur le type Foo, vous obtenez "M" dans la liste intellisense de Foo (en supposant que la classe d'extension est hors de portée). Cela rend 'M' beaucoup plus facile à trouver que MyClass.M (Foo, ...).

À la fin de la journée, c'est juste du sucre syntaxique pour des méthodes ailleurs statiques, mais comme acheter une maison: 'emplacement, emplacement, emplacement!' Si ça dépend du type, les gens le trouveront!

33
Brian

Deux autres avantages des méthodes d'extension que j'ai rencontrés:

  • une interface fluide peut être encapsulée dans une classe statique de méthodes d'extension, réalisant ainsi une séparation des préoccupations entre la classe principale et ses extensions fluentes; j'ai vu que l'on atteignait une plus grande maintenabilité
  • les méthodes d'extension peuvent être suspendues aux interfaces, vous permettant ainsi de spécifier un contrat (via une interface) et une série associée de comportements basés sur l'interface (via les méthodes d'extension), offrant à nouveau une séparation des préoccupations.
29
David Alpert

Certaines des meilleures utilisations que j'ai eues des méthodes d'extension sont la capacité de:

  1. Étendez les fonctionnalités sur des objets tiers (commerciaux ou internes à mon entreprise mais gérés par un groupe distinct), qui dans de nombreux cas seront marqués comme sealed.
  2. Créez des fonctionnalités par défaut pour les interfaces sans avoir à implémenter une classe abstraite

Prends pour exemple, IEnumerable<T>. Bien qu'il soit riche en méthodes d'extension, je l'ai trouvé ennuyeux de ne pas avoir implémenté de méthode générique ForEach. J'ai donc fait le mien:

public void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
{
    foreach ( var o in enumerable )
    {
        action(o);
    }
}

Voila, tout mon IEnumerable<T> les objets, quel que soit le type d'implémentation, et si je l'ai écrit ou si quelqu'un d'autre avait maintenant une méthode ForEach en ajoutant une instruction "using" appropriée dans mon code.

26
Jon Limjap

LINQ est l'une des bonnes raisons d'utiliser des méthodes d'extension. Sans méthodes d'extension, beaucoup de ce que vous pouvez faire dans LINQ serait très difficile. Les méthodes d'extension Where (), Contains (), Select signifient que beaucoup plus de fonctionnalités sont ajoutées aux types existants sans changer leur structure.

12
Ray Booysen

Il y a beaucoup de réponses sur les avantages des méthodes d'extensions; que diriez-vous de répondre aux inconvénients?

Le plus gros inconvénient est qu'il n'y a pas d'erreur de compilation ou d'avertissement si vous avez une méthode régulière et une méthode d'extension avec la même signature dans le même contexte.

Supposons que vous créez une méthode d'extension s'appliquant à une classe particulière. Plus tard, quelqu'un crée une méthode avec une signature identique sur cette classe elle-même.

Votre code sera compilé et vous ne pourrez même pas obtenir d'erreur d'exécution. Mais vous n'exécutez plus le même code qu'auparavant.

9
Ryan Lundy

Interfaces fluides et sensibilité au contexte comme démontré par Greg Young sur CodeBetter

8
cgreeno

Mon argument personnel pour les méthodes d'extension est qu'elles s'intègrent très bien dans une conception OOP: considérez la méthode simple

bool empty = String.IsNullOrEmpty (myString)

en comparaison à

bool empty = myString.IsNullOrEmpty ();
4
Bluenuance

Je voudrais soutenir les autres réponses ici qui mentionnent l'amélioration de la lisibilité du code comme une raison importante derrière les méthodes d'extension. Je vais le démontrer avec deux aspects: le chaînage de méthode par rapport aux appels de méthode imbriqués et l'encombrement d'une requête LINQ avec des noms de classe statiques sans signification.


Prenons cette requête LINQ comme exemple:

numbers.Where(x => x > 0).Select(x => -x)

Where et Select sont des méthodes d'extension définies dans la classe statique Enumerable. Ainsi, si les méthodes d'extension n'existaient pas et qu'il s'agissait de méthodes statiques normales, la dernière ligne de code devrait essentiellement ressembler à ceci:

Enumerable.Select(Enumerable.Where(numbers, x => x > 0), x => -x)

Voyez combien cette requête vient d'être plus méchante.


Deuxièmement, si vous vouliez maintenant introduire votre propre opérateur de requête, vous n'auriez naturellement aucun moyen de le définir à l'intérieur de la classe statique Enumerable, comme tous les autres opérateurs de requête standard, car Enumerable est dans le cadre et vous n'avez aucun contrôle sur cette classe. Par conséquent, vous devez définir votre propre classe statique contenant des méthodes d'extension. Vous pouvez alors obtenir des requêtes telles que celle-ci:

Enumerable.Select(MyEnumerableExtensions.RemoveNegativeNumbers(numbers), x => -x)
//                ^^^^^^^^^^^^^^^^^^^^^^
//                different class name that has zero informational value
//                and, as with 'Enumerable.xxxxxx', only obstructs the
//                query's actual meaning.
4
stakx

Il y a des tas de bonnes réponses ci-dessus sur les méthodes d'extension qui vous permettent de le faire.

Ma réponse courte est - ils éliminent presque le besoin d'usines.

Je vais juste souligner qu'ils ne sont pas un nouveau concept et l'une des plus grandes validations d'entre eux est qu'ils sont une fonctionnalité tueuse dans Objective-C (categories). Ils ajoutent tellement de flexibilité au développement basé sur le framework que NeXT avait NSA et les modélisateurs financiers de Wall Street comme principaux utilisateurs).

REALbasic les implémente également comme étend les méthodes et ils ont été d'une utilité similaire en simplifiant le développement.

4
Andy Dent

Les méthodes d'extension peuvent également aider à garder vos classes et dépendances de classe propres. Par exemple, vous pouvez avoir besoin d'une méthode Bar () pour la classe Foo partout où Foo est utilisé. Cependant, vous souhaiterez peut-être une méthode .ToXml () dans un autre assembly et uniquement pour cet assembly. Dans ce cas, vous pouvez ajouter les dépendances System.Xml et/ou System.Xml.Linq nécessaires dans cet assembly et non dans l'assembly d'origine.

Avantages: les dépendances dans votre classe de définition Assembly sont réduites au strict nécessaire et les autres assemblys consommateurs ne pourront pas utiliser la méthode ToXml (). Voir this PDC presentation pour plus de référence.

3
user29439

Il est vrai que vous pouvez ajouter votre méthode (d'extension) directement dans votre classe. Mais tous les cours ne sont pas écrits par vous. Les classes de la bibliothèque principale ou de bibliothèques tierces sont souvent fermées et il serait impossible d'obtenir le sucre syntaxique sans méthodes d'extension. Mais rappelez-vous, les méthodes d'extension sont exactement comme les méthodes autonomes (statiques) dans eg. c ++

3
Schildmeijer

Les méthodes d'extension sont vraiment l'incorporation .NET du refacteur "Introduce Foreign Method" du livre de Martin Fowler (jusqu'à la signature de la méthode). Ils présentent essentiellement les mêmes avantages et les mêmes pièges. Dans la section sur ce refactor, il dit qu'ils sont une solution de contournement lorsque vous ne pouvez pas modifier la classe qui devrait vraiment être propriétaire de la méthode.

2
Jason Punyon

Je suis d'accord que les méthodes d'extension augmentent la lisibilité du code, mais ce n'est vraiment rien d'autre que des méthodes d'assistance statiques.

IMO utilisant des méthodes d'extension pour ajouter un comportement à vos classes peut être:

Confus: les programmeurs peuvent croire que les méthodes font partie du type étendu, ne comprenant donc pas pourquoi les méthodes ont disparu lorsque l'espace de noms d'extension n'est pas importé.

Un contre-modèle: vous décidez d'ajouter un comportement aux types de votre framework à l'aide de méthodes d'extension, puis de les envoyer à une personne qui les testera. Maintenant, il est coincé avec un cadre contenant un tas de méthodes qu'il ne peut pas simuler.

2
HaraldV

Je vois principalement les méthodes d'extension comme un aveu qu'elles n'auraient peut-être pas dû interdire les fonctions libres.

Dans la communauté C++, il est souvent considéré comme une bonne OOP pratique de préférer les fonctions non membres gratuites aux membres, car ces fonctions ne cassent pas l'encapsulation en accédant aux membres privés dont ils n'ont pas besoin. Extension Les méthodes semblent être un moyen détourné de réaliser la même chose, c'est-à-dire une syntaxe plus propre pour les fonctions statiques qui n'ont pas accès aux membres privés.

Les méthodes d'extension ne sont rien d'autre que du sucre syntaxique, mais je ne vois aucun mal à les utiliser.

2
jalf

Je les utilise pour réutiliser mes classes de modèles d'objets. J'ai un tas de classes qui représentent des objets que j'ai dans une base de données. Ces classes sont utilisées côté client uniquement pour afficher les objets, de sorte que l'utilisation de base consiste à accéder aux propriétés.

public class Stock {
   public Code { get; private set; }
   public Name { get; private set; }
}

En raison de ce modèle d'utilisation, je ne veux pas avoir de méthodes de logique métier dans ces classes, donc je fais de chaque logique métier une méthode d'extension.

public static class StockExtender {
    public static List <Quote> GetQuotesByDate(this Stock s, DateTime date)
    {...}
}

De cette façon, je peux utiliser les mêmes classes pour le traitement de la logique métier et pour l'affichage de l'interface utilisateur sans surcharger le côté client avec du code inutile.

Une chose intéressante à propos de cette solution est que mes classes de modèle d'objet sont générées de manière dynamique en utilisant Mono.Cecil , il serait donc très difficile d'ajouter des méthodes de logique métier même si je le voulais. J'ai un compilateur qui lit les fichiers de définition XML et génère ces classes de stubs représentant certains objets que j'ai dans la base de données. La seule approche dans ce cas est de les étendre.

2
Edwin Jarvis
  • Intellisense sur l'objet lui-même au lieu d'avoir à appeler une fonction d'utilité laide
  • Pour les fonctions de conversion, vous pouvez changer "XToY (X x)" en "ToY (this X x)", ce qui donne un joli x.ToY () au lieu d'un laid XToY (x).
  • Étendez les classes sur lesquelles vous n'avez aucun contrôle
  • Étendre les fonctionnalités des classes lorsqu'il n'est pas souhaitable d'ajouter des méthodes aux classes elles-mêmes. Par exemple, vous pouvez conserver des objets métier simples et sans logique, et ajouter une logique métier spécifique avec des dépendances moches dans les méthodes d'extension
2
davogones

Il permet à C # de mieux prendre en charge les langages dynamiques, LINQ et une douzaine d'autres choses. Découvrez article de Scott Guthrie .

1
DavGarcia

Un cas où les méthodes d'extension étaient très utiles était dans une application cliente qui utilise les services Web ASMX. En raison de la sérialisation, les types de retour des méthodes Web ne contiennent aucune méthode (seules les propriétés publiques de ces types sont disponibles sur le client).

Les méthodes d'extension ont permis d'utiliser pour ajouter des fonctionnalités (côté client) aux types renvoyés par les méthodes Web sans avoir à créer encore un autre modèle d'objet ou de nombreuses classes wrapper du côté client.

1
M4N

Dans mon dernier projet, j'ai utilisé la méthode d'extension pour attacher des méthodes Validate () aux objets métier. J'ai justifié cela parce que les objets métier où les données sérialisables transfèrent des objets et seront utilisés dans des domaines différents comme ils le sont dans des entités de commerce électronique générales telles que produit, client, commerçant, etc. Logique de validation liée tardivement dans une méthode Validate attachée à la classe de base de mes objets de transfert de données. J'espère que cela a du sens :)

1
Athens

Souvenez-vous également que les méthodes d'extension ont été ajoutées pour aider la requête Linq à être plus lisible, lorsqu'elles sont utilisées dans leur style C #.

Ces 2 affectations sont absolument équivalentes, mais la première est beaucoup plus lisible (et l'écart de lisibilité augmenterait bien sûr avec plus de méthodes enchaînées).

int n1 = new List<int> {1,2,3}.Where(i => i % 2 != 0).Last();

int n2 = Enumerable.Last(Enumerable.Where(new List<int> {1,2,3}, i => i % 2 != 0));

Notez que la syntaxe complète doit même être:

int n1 = new List<int> {1,2,3}.Where<int>(i => i % 2 != 0).Last<int>();

int n2 = Enumerable.Last<int>(Enumerable.Where<int>(new List<int> {1,2,3}, i => i % 2 != 0));

Par hasard, les paramètres de type de Where et Last n'ont pas besoin d'être explicitement mentionnés car ils peuvent être déduits grâce à la présence du premier paramètre de ces deux méthodes (le paramètre qui est introduit par le mot clé this et en faire des méthodes d'extension).

Ce point est évidemment un avantage (entre autres) des méthodes d'extension, et vous pouvez en profiter dans tous les scénarios similaires où le chaînage de méthodes est impliqué.

Surtout, c'est la manière la plus élégante et convaincante que j'ai trouvée d'avoir une méthode de classe de base invocable par n'importe quelle sous-classe et renvoyant une référence fortement typée à cette sous-classe (avec le type de sous-classe).

Exemple (ok, ce scénario est totalement ringard): après une bonne nuit, un animal ouvre les yeux puis pousse un cri; chaque animal ouvre les yeux de la même manière, alors qu'un chien aboie et un canard kwaks.

public abstract class Animal
{
    //some code common to all animals
}

public static class AnimalExtension
{
    public static TAnimal OpenTheEyes<TAnimal>(this TAnimal animal) where TAnimal : Animal
    {
        //Some code to flutter one's eyelashes and then open wide
        return animal; //returning a self reference to allow method chaining
    }
}

public class Dog : Animal
{
    public void Bark() { /* ... */ }
}

public class Duck : Animal
{
    public void Kwak() { /* ... */ }
}

class Program
{
    static void Main(string[] args)
    {
        Dog Goofy = new Dog();
        Duck Donald = new Duck();
        Goofy.OpenTheEyes().Bark(); //*1
        Donald.OpenTheEyes().Kwak(); //*2
    }
}

Conceptuellement, OpenTheEyes devrait être une méthode Animal, mais elle retournerait alors une instance de la classe abstraite Animal, qui ne connaît pas les méthodes de sous-classe spécifiques comme Bark ou Duck ou autre chose. Les 2 lignes commentées comme * 1 et * 2 déclencheraient alors une erreur de compilation.

Mais grâce aux méthodes d'extension, nous pouvons avoir une sorte de "méthode de base qui connaît le type de sous-classe sur lequel elle est appelée".

Notez qu'une simple méthode générique aurait pu faire le travail, mais d'une manière beaucoup plus maladroite:

public abstract class Animal
{
    //some code common to all animals

    public TAnimal OpenTheEyes<TAnimal>() where TAnimal : Animal
    {
        //Some code to flutter one's eyelashes and then open wide
        return (TAnimal)this; //returning a self reference to allow method chaining
    }
}

Cette fois, aucun paramètre et donc aucune inférence de type de retour possible. L'appel ne peut être rien d'autre que:

Goofy.OpenTheEyes<Dog>().Bark();
Donald.OpenTheEyes<Duck>().Kwak();

... qui peut peser beaucoup sur le code si plus de chaînage est impliqué (surtout en sachant que le paramètre type sera toujours <Dog> sur la ligne de Goofy et <Duck> celui de Donald ...)

1
Ssithra

J'ai trouvé que les méthodes d'extension sont utiles pour faire correspondre les arguments génériques imbriqués.

Cela semble un peu bizarre - mais disons que nous avons une classe générique MyGenericClass<TList>, et nous savons que TList lui-même est générique (par exemple, un List<T>), Je ne pense pas qu'il existe un moyen de déterrer ce "T" imbriqué de la liste sans méthodes d'extension ou méthodes d'assistance statiques. Si nous n'avons que des méthodes d'assistance statiques à notre disposition, c'est (a) moche, et (b) nous forcera à déplacer la fonctionnalité qui appartient à la classe vers un emplacement externe.

par exemple. pour récupérer les types dans un Tuple et les convertir en signature de méthode, nous pouvons utiliser des méthodes d'extension:

public class Tuple { }
public class Tuple<T0> : Tuple { }
public class Tuple<T0, T1> : Tuple<T0> { }

public class Caller<TTuple> where TTuple : Tuple { /* ... */ }

public static class CallerExtensions
{
     public static void Call<T0>(this Caller<Tuple<T0>> caller, T0 p0) { /* ... */ }

     public static void Call<T0, T1>(this Caller<Tuple<T0, T1>> caller, T0 p0, T1 p1) { /* ... */ }
}

new Caller<Tuple<int>>().Call(10);
new Caller<Tuple<string, int>>().Call("Hello", 10);

Cela dit, je ne sais pas où la ligne de démarcation devrait être - quand une méthode devrait-elle être une méthode d'extension et quand devrait-elle être une méthode d'assistance statique? Des pensées?

0
Andy

Je pense que les méthodes d'extension aident à écrire du code plus clair.

Au lieu de mettre une nouvelle méthode dans votre classe, comme votre ami l'a suggéré, vous la placez dans l'espace de noms ExtensionMethods. De cette façon, vous maintenez un sens logique de l'ordre à votre classe. Les méthodes qui ne traitent pas vraiment directement avec votre classe ne l'encombreront pas.

Je pense que les méthodes d'extension rendent votre code plus clair et mieux organisé.

0
Alex Baranosky

Je les aime pour avoir construit du HTML. Il y a souvent des sections qui sont utilisées à plusieurs reprises, ou générées de manière récursive où une fonction est utile mais qui autrement interromprait le flux du programme.

        HTML_Out.Append("<ul>");
        foreach (var i in items)
            if (i.Description != "")
            {
                HTML_Out.Append("<li>")
                    .AppendAnchor(new string[]{ urlRoot, i.Description_Norm }, i.Description)
                    .Append("<div>")
                    .AppendImage(iconDir, i.Icon, i.Description)
                    .Append(i.Categories.ToHTML(i.Description_Norm, urlRoot)).Append("</div></li>");
            }

        return HTML_Out.Append("</ul>").ToString();

Il existe également des situations où un objet a besoin d'une logique personnalisée pour être préparé pour les méthodes d'extension de sortie HTML vous permettant d'ajouter cette fonctionnalité sans mélanger présentation et logique au sein de la classe.

0
Kelly Gendron

Il y a tellement d'excellents exemples de méthodes d'extension .. en particulier sur IEnumerables comme indiqué ci-dessus.

par exemple. si j'ai un IEnumerable<myObject> Je peux créer et étendre la méthode pour IEnumerable<myObject>

mylist List<myObject>;

... créer la liste

mylist.DisplayInMyWay();

Sans extension, les méthodes devraient appeler:

myDisplayMethod(myOldArray); // can create more nexted brackets.

un autre excellent exemple est la création d'une liste liée circulaire en un clin d'œil!

Je peux m'en attribuer le mérite!

liste liée circulaire utilisant des méthodes d'extension

Maintenant, combinez-les et utilisez le code des méthodes d'extension comme suit.

myNode.NextOrFirst().DisplayInMyWay();

plutôt que

DisplayInMyWay(NextOrFirst(myNode)).

en utilisant des méthodes d'extension Il est plus net et plus facile à lire et plus orienté objet. également très proche de:

myNode.Next.DoSomething()

Montrez cela à votre collègue! :)

0
Gregor

Les méthodes d'extension peuvent être utilisées pour créer un sorte de mixin en C #.

Ceci, à son tour, permet une meilleure séparation des préoccupations pour les concepts orthogonaux. Jetez un oeil à ceci réponse comme exemple.

Cela peut également être utilisé pour activer les rôles en C #, un concept central de la architecture DCI .

0
Jordão

Il permet à votre éditeur/IDE de faire une suggestion de saisie automatique intelligente.

0
Dennis C

J'ai des zones de saisie sur mon écran, et toutes doivent implémenter un comportement standard quels que soient leurs types exacts (zones de texte, cases à cocher, etc.). Ils ne peuvent pas hériter d'une classe de base commune car chaque type de zone d'entrée dérive déjà d'une classe spécifique (TextInputBox, etc.)

Peut-être qu'en remontant dans la hiérarchie d'héritage, je pourrais trouver un ancêtre commun comme disons WebControl, mais je n'ai pas développé la classe de framework WebControl et elle n'expose pas ce dont j'ai besoin.

Avec la méthode d'extension, je peux:

1) étendre la classe WebControl, puis obtenir mon comportement standard unifié sur toutes mes classes d'entrée

2) faire alternativement toutes mes classes dériver d'une interface, disons IInputZone, et étendre cette interface avec des méthodes. Je vais maintenant pouvoir appeler des méthodes d'extensions liées à l'interface sur toutes mes zones de saisie. J'ai ainsi réalisé une sorte d'héritage multiple puisque mes zones d'entrée dérivaient déjà de plusieurs classes de base.

0
Ssithra

Je n'ai qu'un mot à dire à ce sujet: MAINTENABILITÉ c'est la clé pour l'utilisation des méthodes d'extension

0
Tamir