web-dev-qa-db-fra.com

Puis-je ajouter des méthodes d'extension à une classe statique existante?

Je suis un fan des méthodes d'extension en C #, mais je n'ai pas réussi à ajouter une méthode d'extension à une classe statique, telle que Console.

Par exemple, si je veux ajouter une extension à la console, appelée 'WriteBlueLine', pour pouvoir aller:

Console.WriteBlueLine("This text is blue");

J'ai essayé cela en ajoutant une méthode statique publique locale, avec la console comme paramètre 'this' ... mais pas de dés!

public static class Helpers {
    public static void WriteBlueLine(this Console c, string text)
    {
        Console.ForegroundColor = ConsoleColor.Blue;
        Console.WriteLine(text);
        Console.ResetColor();
    }
}

Cela n'a pas ajouté une méthode 'WriteBlueLine' à la console ... est-ce que je me trompe? Ou demander l'impossible?

506
Leon Bambrick

Les méthodes d'extension nécessitent une variable d'instance (valeur) pour un objet. Vous pouvez cependant écrire un wrapper statique autour de l'interface ConfigurationManager. Si vous implémentez l'encapsuleur, vous n'avez pas besoin d'une méthode d'extension car vous pouvez simplement ajouter la méthode directement.

 public static class ConfigurationManagerWrapper
 {
      public static ConfigurationSection GetSection( string name )
      {
         return ConfigurationManager.GetSection( name );
      }

      .....

      public static ConfigurationSection GetWidgetSection()
      {
          return GetSection( "widgets" );
      }
 }
263
tvanfosson

Pouvez-vous ajouter des extensions statiques aux classes en C #? Non mais tu peux faire ça:

public static class Extensions
{
    public static T Create<T>(this T @this)
        where T : class, new()
    {
        return Utility<T>.Create();
    }
}

public static class Utility<T>
    where T : class, new()
{
    static Utility()
    {
        Create = Expression.Lambda<Func<T>>(Expression.New(typeof(T).GetConstructor(Type.EmptyTypes))).Compile();
    }
    public static Func<T> Create { get; private set; }
}

Voilà comment cela fonctionne. Bien qu'il soit techniquement impossible d'écrire des méthodes d'extension statiques, ce code exploite une faille dans les méthodes d'extension. Cette lacune étant que vous pouvez appeler des méthodes d'extension sur des objets null sans obtenir l'exception (sauf si vous accédez à quoi que ce soit via @this).

Alors, voici comment vous utiliseriez ceci:

    var ds1 = (null as DataSet).Create(); // as oppose to DataSet.Create()
    // or
    DataSet ds2 = null;
    ds2 = ds2.Create();

    // using some of the techniques above you could have this:
    (null as Console).WriteBlueLine(...); // as oppose to Console.WriteBlueLine(...)

Maintenant, POURQUOI ai-je choisi d'appeler le constructeur par défaut à titre d'exemple, ET ET pourquoi ne pas simplement renvoyer new T() dans le premier fragment de code sans faire tout le bazar d'Expression? Eh bien, aujourd’hui, votre jour de chance parce que vous obtenez un 2fer. Comme tous les développeurs .NET avancés le savent, new T() est lent car il génère un appel à System.Activator qui utilise la réflexion pour obtenir le constructeur par défaut avant de l'appeler. Bon sang Microsoft! Cependant, mon code appelle directement le constructeur par défaut de l'objet.

Les extensions statiques seraient meilleures que cela, mais les temps désespérés appellent des mesures désespérées.

89
Mr. Obnoxious

Ce n'est pas possible.

Et oui, je pense que MS a commis une erreur ici.

Leur décision n'a pas de sens et oblige les programmeurs à écrire (comme décrit ci-dessus) une classe de wrapper inutile.

Voici un bon exemple: Essayer d’étendre la classe de tests statiques d’unités MS statiques Assert: je veux encore une méthode Assert AreEqual(x1,x2).

La seule façon de faire est de pointer vers différentes classes ou d'écrire un wrapper autour de 100 méthodes d'assertions différentes. Pourquoi!?

Si la décision a été prise d'autoriser les extensions d'instances, je ne vois aucune raison logique de ne pas autoriser les extensions statiques. Les arguments concernant la section des bibliothèques ne sont pas valables une fois que les instances peuvent être étendues.

45
Tom Deloford

Je suis tombé sur ce fil en essayant de trouver une réponse à la même question que le PO. Je n'ai pas trouvé la réponse que je voulais mais j'ai fini par le faire.

public static class MyConsole
{
    public static void WriteLine(this ConsoleColor Color, string Text)
    {
        Console.ForegroundColor = Color;
        Console.WriteLine(Text);   
    }
}

Et je l'utilise comme ça:

ConsoleColor.Cyan.WriteLine("voilà");
19
Adel G.Eibesh

Peut-être pourriez-vous ajouter une classe statique avec votre espace de noms personnalisé et le même nom de classe:

using CLRConsole = System.Console;

namespace ExtensionMethodsDemo
{
    public static class Console
    {
        public static void WriteLine(string value)
        {
            CLRConsole.WriteLine(value);
        }

        public static void WriteBlueLine(string value)
        {
            System.ConsoleColor currentColor = CLRConsole.ForegroundColor;

            CLRConsole.ForegroundColor = System.ConsoleColor.Blue;
            CLRConsole.WriteLine(value);

            CLRConsole.ForegroundColor = currentColor;
        }

        public static System.ConsoleKeyInfo ReadKey(bool intercept)
        {
            return CLRConsole.ReadKey(intercept);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Console.WriteBlueLine("This text is blue");   
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }

            Console.WriteLine("Press any key to continue...");
            Console.ReadKey(true);
        }
    }
}
17
Pag Sun
10
mbx

Nan. Les définitions de méthodes d'extension nécessitent une instance du type que vous étendez. C'est regrettable; Je ne sais pas pourquoi c'est nécessaire ...

10
Will

En ce qui concerne les méthodes d'extension, les méthodes d'extension elles-mêmes sont statiques; mais elles sont appelées comme s'il s'agissait de méthodes d'instance. Puisqu'une classe statique n'est pas instanciable, vous n'auriez jamais d'instance de la classe à partir de laquelle invoquer une méthode d'extension. Pour cette raison, le compilateur n'autorise pas la définition de méthodes d'extension pour les classes statiques.

M. Obnoxious a écrit: "Comme le savent tous les développeurs .NET avancés, new T() est lent car il génère un appel à System.Activator qui utilise la réflexion pour obtenir le constructeur par défaut avant de l'appeler".

New () est compilé dans l'instruction IL "newobj" si le type est connu au moment de la compilation. Newobj prend un constructeur pour une invocation directe. Les appels à System.Activator.CreateInstance () sont compilés en instruction "call" IL pour appeler System.Activator.CreateInstance (). New (), lorsqu'il est utilisé contre des types génériques, entraîne un appel à System.Activator.CreateInstance (). Le message de M. Obnoxious n'était pas clair sur ce point ... et bien, odieux.

Ce code:

System.Collections.ArrayList _al = new System.Collections.ArrayList();
System.Collections.ArrayList _al2 = (System.Collections.ArrayList)System.Activator.CreateInstance(typeof(System.Collections.ArrayList));

produit cette IL:

  .locals init ([0] class [mscorlib]System.Collections.ArrayList _al,
           [1] class [mscorlib]System.Collections.ArrayList _al2)
  IL_0001:  newobj     instance void [mscorlib]System.Collections.ArrayList::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldtoken    [mscorlib]System.Collections.ArrayList
  IL_000c:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_0011:  call       object [mscorlib]System.Activator::CreateInstance(class [mscorlib]System.Type)
  IL_0016:  castclass  [mscorlib]System.Collections.ArrayList
  IL_001b:  stloc.1
7
Brian Griffin

Vous ne pouvez pas ajouter des méthodes statique à un type. Vous ne pouvez ajouter que des méthodes (pseudo-) instance à une instance d'un type.

Le point du modificateur this consiste à indiquer au compilateur C # de transmettre l'occurrence située à gauche du . en tant que premier paramètre de la méthode statique/extension.

Dans le cas de l'ajout de méthodes statiques à un type, il n'y a pas d'instance à transmettre pour le premier paramètre.

5
Brannon

J'ai essayé de faire cela avec System.Environment lorsque j'apprenais des méthodes d'extension et cela n'a pas abouti. La raison en est, comme d'autres l'ont mentionné, car les méthodes d'extension nécessitent une instance de la classe.

4
Robert S.

Il n'est pas possible d'écrire une méthode d'extension, mais il est possible d'imiter le comportement que vous demandez.

using FooConsole = System.Console;

public static class Console
{
    public static void WriteBlueLine(string text)
    {
        FooConsole.ForegroundColor = ConsoleColor.Blue;
        FooConsole.WriteLine(text);
        FooConsole.ResetColor();
    }
}

Cela vous permettra d'appeler Console.WriteBlueLine (fooText) dans d'autres classes. Si les autres classes veulent accéder aux autres fonctions statiques de la console, elles devront être explicitement référencées via leur espace de noms.

Vous pouvez toujours ajouter toutes les méthodes dans la classe de remplacement si vous souhaitez les avoir toutes au même endroit.

Donc, vous auriez quelque chose comme

using FooConsole = System.Console;

public static class Console
{
    public static void WriteBlueLine(string text)
    {
        FooConsole.ForegroundColor = ConsoleColor.Blue;
        FooConsole.WriteLine(text);
        FooConsole.ResetColor();
    }
    public static void WriteLine(string text)
    {
        FooConsole.WriteLine(text);
    }
...etc.
}

Cela fournirait le genre de comportement que vous recherchez.

* Remarque La console devra être ajoutée via l'espace de noms dans lequel vous l'avez insérée.

2
Douglas Potesta

oui, dans un sens limité.

public class DataSet : System.Data.DataSet
{
    public static void SpecialMethod() { }
}

Cela fonctionne mais la console ne le fait pas car c'est statique.

public static class Console
{       
    public static void WriteLine(String x)
    { System.Console.WriteLine(x); }

    public static void WriteBlueLine(String x)
    {
        System.Console.ForegroundColor = ConsoleColor.Blue;
        System.Console.Write(.x);           
    }
}

Cela fonctionne car tant que ce n'est pas sur le même espace de noms. Le problème est que vous devez écrire une méthode statique proxy pour chaque méthode de System.Console. Ce n'est pas nécessairement une mauvaise chose car vous pouvez ajouter quelque chose comme ceci:

    public static void WriteLine(String x)
    { System.Console.WriteLine(x.Replace("Fck","****")); }

ou

 public static void WriteLine(String x)
    {
        System.Console.ForegroundColor = ConsoleColor.Blue;
        System.Console.WriteLine(x); 
    }

La façon dont cela fonctionne est que vous accrochez quelque chose dans la WriteLine standard. Il peut s'agir d'un nombre de lignes ou d'un filtre Word incorrect ou autre. Chaque fois que vous spécifiez simplement Console dans votre espace de noms, dites WebProject1 et importez le système System, WebProject1.Console sera choisi par défaut sur System.Console pour les classes de l'espace de nom WebProject1. Donc, ce code va transformer tous les appels Console.WriteLine en bleu dans la mesure où vous n'avez jamais spécifié System.Console.WriteLine.

1
Black Dog

Ce qui suit a été rejeté en tant que modification de la réponse de tvanfosson. On m'a demandé d'y contribuer comme ma propre réponse. J'ai utilisé sa suggestion et terminé la mise en œuvre d'un wrapper ConfigurationManager. En principe, j'ai simplement rempli le ... dans la réponse de tvanfosson.

Les méthodes d'extension nécessitent une instance d'un objet. Vous pouvez cependant écrire un wrapper statique autour de l'interface ConfigurationManager. Si vous implémentez l'encapsuleur, vous n'avez pas besoin d'une méthode d'extension car vous pouvez simplement ajouter la méthode directement.

public static class ConfigurationManagerWrapper
{
    public static NameValueCollection AppSettings
    {
        get { return ConfigurationManager.AppSettings; }
    }

    public static ConnectionStringSettingsCollection ConnectionStrings
    {
        get { return ConfigurationManager.ConnectionStrings; }
    }

    public static object GetSection(string sectionName)
    {
        return ConfigurationManager.GetSection(sectionName);
    }

    public static Configuration OpenExeConfiguration(string exePath)
    {
        return ConfigurationManager.OpenExeConfiguration(exePath);
    }

    public static Configuration OpenMachineConfiguration()
    {
        return ConfigurationManager.OpenMachineConfiguration();
    }

    public static Configuration OpenMappedExeConfiguration(ExeConfigurationFileMap fileMap, ConfigurationUserLevel userLevel)
    {
        return ConfigurationManager.OpenMappedExeConfiguration(fileMap, userLevel);
    }

    public static Configuration OpenMappedMachineConfiguration(ConfigurationFileMap fileMap)
    {
        return ConfigurationManager.OpenMappedMachineConfiguration(fileMap);
    }

    public static void RefreshSection(string sectionName)
    {
        ConfigurationManager.RefreshSection(sectionName);
    }
}
1
André C. Andersen

Vous pouvez utiliser un casting sur null pour le faire fonctionner.

public static class YoutTypeExtensionExample
{
    public static void Example()
    {
        ((YourType)null).ExtensionMethod();
    }
}

L'extension:

public static class YourTypeExtension
{
    public static void ExtensionMethod(this YourType x) { }
}

Ton type:

public class YourType { }
0
Wouter