Posso aggiungere metodi di estensione a una classe statica esistente?


535

Sono un fan dei metodi di estensione in C #, ma non ho avuto alcun successo nell'aggiungere un metodo di estensione a una classe statica, come Console.

Ad esempio, se voglio aggiungere un'estensione alla console, denominata "WriteBlueLine", in modo che io possa andare:

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

Ho provato questo aggiungendo un metodo statico pubblico locale, con Console come parametro "questo" ... ma niente dadi!

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

Questo non ha aggiunto un metodo 'WriteBlueLine' alla console ... sto sbagliando? O chiedendo l'impossibile?


3
Oh bene. sfortunato ma penso che ci riuscirò. Sono ANCORA un metodo di estensione vergine (comunque nel codice di produzione). Forse un giorno, se sono fortunato.
Andy McClubag,

Ho scritto una serie di estensioni HtmlHelper per ASP.NET MVC. Ne ho scritto uno per DateTime per darmi la fine della data indicata (23: 59.59). Utile quando chiedi all'utente di specificare una data di fine, ma vuoi davvero che sia la fine di quel giorno.
tvanfosson,

12
Al momento non è possibile aggiungerli perché la funzione non esiste in C #. Non perché sia ​​impossibile di per sé , ma poiché i pip di C # sono molto occupati, erano per lo più interessati ai metodi di estensione per far funzionare LINQ e non vedevano abbastanza benefici nei metodi di estensione statici da giustificare il tempo che avrebbero impiegato per implementare. Eric Lippert spiega qui .
Jordan Gray,

1
Basta chiamare Helpers.WriteBlueLine(null, "Hi");:)
Hüseyin Yağlı

Risposte:


286

No. I metodi di estensione richiedono una variabile di istanza (valore) per un oggetto. Tuttavia, è possibile scrivere un wrapper statico ConfigurationManagernell'interfaccia. Se si implementa il wrapper, non è necessario un metodo di estensione poiché è possibile aggiungere direttamente il metodo.

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

      .....

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

8
@Luis - nel contesto, l'idea sarebbe "potrei aggiungere un metodo di estensione alla classe ConfigurationManager per ottenere una sezione specifica?" Non è possibile aggiungere un metodo di estensione a una classe statica poiché richiede un'istanza dell'oggetto, ma è possibile scrivere una classe wrapper (o facciata) che implementa la stessa firma e difende la chiamata effettiva al vero ConfigurationManager. È possibile aggiungere qualsiasi metodo desiderato alla classe wrapper in modo che non debba essere un'estensione.
tvanfosson,

Trovo più utile aggiungere un metodo statico alla classe che implementa ConfigurationSection. Quindi, data un'implementazione chiamata MyConfigurationSection, chiamerei MyConfigurationSection.GetSection (), che restituisce la sezione già digitata o null se non esiste. Il risultato finale è lo stesso, ma evita di aggiungere una classe.
tocca l'

1
@tap - è solo un esempio e il primo che mi è venuto in mente. Tuttavia, entra in gioco il principio della responsabilità unica. Il "contenitore" dovrebbe effettivamente essere responsabile dell'interpretazione del file di configurazione? Normalmente ho semplicemente ConfigurationSectionHandler e lancio l'output da ConfigurationManager nella classe appropriata e non mi preoccupo del wrapper.
tvanfosson,

5
Per uso interno, ho iniziato a creare varianti "X" di classi e strutture statiche per l'aggiunta di estensioni personalizzate: "ConsoleX" contiene nuovi metodi statici per "Console", "MathX" contiene nuovi metodi statici per "Math", "ColorX" estende i metodi "Colore", ecc. Non è lo stesso, ma è facile da ricordare e scoprire in IntelliSense.
user1689175,

1
@Xtro Sono d'accordo che è terribile, ma non peggio che non essere in grado di utilizzare un doppio test al suo posto o, peggio ancora, rinunciare a testare il codice perché le classi statiche lo rendono così difficile. Microsoft sembra essere d'accordo con me perché è il motivo per cui hanno introdotto le classi HttpContextWrapper / HttpContextBase per aggirare il HttpContext.Current statico per MVC.
tvanfosson,

92

Puoi aggiungere estensioni statiche alle classi in C #? No, ma puoi farlo:

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; }
}

Ecco come funziona. Sebbene non sia possibile scrivere tecnicamente metodi di estensione statici, questo codice sfrutta una lacuna nei metodi di estensione. La scappatoia è che puoi chiamare metodi di estensione su oggetti null senza ottenere l'eccezione nulla (a meno che tu non acceda a qualcosa tramite @this).

Quindi ecco come lo useresti:

    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(...)

Ora PERCHÉ ho scelto di chiamare il costruttore predefinito come esempio e E perché non restituisco semplicemente T () nel primo frammento di codice senza fare tutta quella spazzatura di espressioni? Bene oggi è il tuo giorno fortunato perché ottieni un 2fer. Come qualsiasi sviluppatore .NET avanzato sa, il nuovo T () è lento perché genera una chiamata a System.Activator che utilizza la riflessione per ottenere il costruttore predefinito prima di chiamarlo. Accidenti a te Microsoft! Tuttavia il mio codice chiama direttamente il costruttore predefinito dell'oggetto.

Le estensioni statiche sarebbero meglio di così, ma i tempi disperati richiedono misure disperate.


2
Penso che per Dataset questo trucco funzionerà, ma dubito che funzioni per la classe Console poiché la Console è classe statica, i tipi statici non possono essere usati come argomenti :)
ThomasBecker,

Sì, stavo per dire la stessa cosa. Si tratta di metodi di estensione pseudo-statici su una classe non statica. L'OP era un metodo di estensione su una classe statica.
Mark A. Donohoe,

2
E 'molto meglio e più facile da solo avere un po' convenzione di denominazione per tali metodi come XConsole, ConsoleHelpere così via.
Alex Zhukovskiy,

9
Questo è un trucco affascinante, ma il risultato è maleodorante. Si crea un oggetto null, quindi sembra chiamare un metodo su di esso - nonostante anni siano stati detti che "chiamare un metodo su un oggetto null provoca un'eccezione". Funziona, ma ..ugh ... Confusione per chiunque mantenga più tardi. Non voglio sottovalutare, perché hai aggiunto al pool di informazioni su ciò che è possibile. Ma spero sinceramente che nessuno usi mai questa tecnica !! Ulteriore lamentela: non passare uno di questi a un metodo e aspettarsi di ottenere una sottoclasse OO: il metodo chiamato sarà il tipo di dichiarazione dei parametri e non il tipo di parametro passato .
ToolmakerSteve

5
Questo è difficile, ma mi piace. Un'alternativa a (null as DataSet).Create();potrebbe essere default(DataSet).Create();.
Bagerfahrer,

54

Non è possibile.

E sì, penso che la SM abbia fatto un errore qui.

La loro decisione non ha senso e costringe i programmatori a scrivere (come descritto sopra) una classe wrapper inutile.

Ecco un buon esempio: cercare di estendere la classe di test statica MS Unit Assert: voglio un altro metodo Assert AreEqual(x1,x2).

L'unico modo per farlo è quello di puntare a classi diverse o scrivere un wrapper attorno a centinaia di metodi Assert diversi. Perché!?

Se è stata presa la decisione di consentire estensioni di istanze, non vedo alcun motivo logico per non consentire estensioni statiche. Gli argomenti sulle librerie di sezionamento non si sollevano una volta che le istanze possono essere estese.


20
Stavo anche cercando di estendere la classe MS Unit Test Assert per aggiungere Assert.Throws e Assert.DoesNotThrow e ho riscontrato lo stesso problema.
Stefano Ricciardi,

3
Sì anch'io :( ho pensato che posso fare Assert.Throwsalla risposta stackoverflow.com/questions/113395/...
CallMeLaNN

27

Mi sono imbattuto in questo thread mentre cercavo di trovare una risposta alla stessa domanda dell'OP. Non ho trovato la risposta che volevo, ma ho finito per farlo.

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

E lo uso così:

ConsoleColor.Cyan.WriteLine("voilà");

19

Forse potresti aggiungere una classe statica con il tuo spazio dei nomi personalizzato e lo stesso nome di 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);
        }
    }
}

1
Ma ciò non risolve il problema della necessità di reimplementare ogni singolo metodo dalla classe statica originale che si desidera conservare nel proprio wrapper. È ancora un wrapper, anche se ha il merito di aver bisogno di meno cambiamenti nel codice che lo utilizza ...
binki


11

No. Le definizioni del metodo di estensione richiedono un'istanza del tipo che si sta estendendo. È sfortunato; Non sono sicuro del motivo per cui è richiesto ...


4
È perché viene utilizzato un metodo di estensione per estendere un'istanza di un oggetto. Se non lo facessero, sarebbero solo normali metodi statici.
Derek Ekins,

31
Sarebbe bello fare entrambe le cose, no?

7

Per quanto riguarda i metodi di estensione, i metodi di estensione stessi sono statici; ma vengono invocati come se fossero metodi di istanza. Poiché una classe statica non è istantanea, non si dovrebbe mai avere un'istanza della classe da cui richiamare un metodo di estensione. Per questo motivo il compilatore non consente di definire metodi di estensione per le classi statiche.

Obnoxious ha scritto: "Come tutti gli sviluppatori .NET avanzati sanno, il nuovo T () è lento perché genera una chiamata a System.Activator che utilizza la riflessione per ottenere il costruttore predefinito prima di chiamarlo".

New () viene compilato nell'istruzione IL "newobj" se il tipo è noto al momento della compilazione. Newobj prende un costruttore per l'invocazione diretta. Le chiamate a System.Activator.CreateInstance () compilano l'istruzione "call" di IL per invocare System.Activator.CreateInstance (). New () se utilizzato con tipi generici comporterà una chiamata a System.Activator.CreateInstance (). Il post di Mr. Obnoxious non era chiaro su questo punto ... e beh, odioso.

Questo codice:

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

produce questo 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

5

Non è possibile aggiungere metodi statici a un tipo. È possibile aggiungere solo metodi (pseudo-) istanza a un'istanza di un tipo.

Il punto del thismodificatore è dire al compilatore C # di passare l'istanza sul lato sinistro di .come primo parametro del metodo statico / di estensione.

Nel caso di aggiunta di metodi statici a un tipo, non vi è istanza da passare per il primo parametro.


4

Ho provato a farlo con System.Environment quando stavo imparando i metodi di estensione e non ho avuto successo. Il motivo è, come altri menzionano, perché i metodi di estensione richiedono un'istanza della classe.


3

Non è possibile scrivere un metodo di estensione, tuttavia è possibile imitare il comportamento richiesto.

using FooConsole = System.Console;

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

Ciò ti consentirà di chiamare Console.WriteBlueLine (fooText) in altre classi. Se le altre classi vogliono accedere alle altre funzioni statiche della Console, dovranno essere esplicitamente referenziate attraverso il loro spazio dei nomi.

Puoi sempre aggiungere tutti i metodi alla classe di sostituzione se vuoi averli tutti in un unico posto.

Quindi avresti qualcosa del genere

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.
}

Ciò fornirebbe il tipo di comportamento che stai cercando.

* Nota La console dovrà essere aggiunta tramite lo spazio dei nomi in cui è stata inserita.


1

sì, in senso limitato.

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

Funziona ma la console no perché è statica.

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);           
    }
}

Questo funziona perché purché non si trovi sullo stesso spazio dei nomi. Il problema è che devi scrivere un metodo statico proxy per ogni metodo di System.Console. Non è necessariamente una cosa negativa in quanto puoi aggiungere qualcosa del genere:

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

o

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

Il modo in cui funziona è che si aggancia qualcosa allo standard WriteLine. Potrebbe essere un conteggio delle righe o un filtro per parolacce o altro. Ogni volta che si specifica Console nel proprio spazio dei nomi, si dice WebProject1 e si importa lo spazio dei nomi Sistema, WebProject1.Console verrà scelto su System.Console come predefinito per quelle classi nello spazio dei nomi WebProject1. Quindi questo codice trasformerà tutte le chiamate Console.WriteLine in blu nella misura in cui non hai mai specificato System.Console.WriteLine.


sfortunatamente l'approccio dell'uso di un discendente non funziona quando la classe base è sigillata (come molti nella libreria di classi .NET)
George Birbilis,

1

Quanto segue è stato respinto come modifica alla risposta di tvanfosson. Mi è stato chiesto di contribuire come mia risposta. Ho usato il suo suggerimento e ho completato l'implementazione di un ConfigurationManagerwrapper. In linea di principio ho semplicemente compilato la ...risposta di in tvanfosson.

No. I metodi di estensione richiedono un'istanza di un oggetto. Tuttavia, è possibile scrivere un wrapper statico nell'interfaccia di ConfigurationManager. Se si implementa il wrapper, non è necessario un metodo di estensione poiché è possibile aggiungere direttamente il metodo.

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);
    }
}

0

Puoi usare un cast su null per farlo funzionare.

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

L'estensione:

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

Il tuo tipo:

public class YourType { }

-4

Puoi farlo se sei disposto a "fregarlo" un po 'creando una variabile della classe statica e assegnandola a null. Tuttavia, questo metodo non sarebbe disponibile per le chiamate statiche sulla classe, quindi non sono sicuro di quanto uso sarebbe:

Console myConsole = null;
myConsole.WriteBlueLine("my blue line");

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

questo è esattamente quello che ho fatto. La mia classe si chiama MyTrace :)
Gishu,

Consiglio utile un po 'di odore di codice, ma immagino che potremmo nascondere l'oggetto null in una classe base o qualcosa del genere. Grazie.
Tom Deloford,

1
Non riesco a compilare questo codice. Errore "System.Console": i tipi statici non possono essere utilizzati come parametri
kuncevic.dev

Sì, questo non può essere fatto. Accidenti pensavo fossi su qualcosa lì! I tipi statici non possono essere passati come parametri in metodi che suppongo abbiano senso. Speriamo solo che MS veda il legno dagli alberi su questo e lo cambi.
Tom Deloford,

3
Avrei dovuto provare a compilare il mio codice! Come dice Tom, questo non funzionerà con le classi statiche.
Tenaka,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.