Kann ich einer vorhandenen statischen Klasse Erweiterungsmethoden hinzufügen?

534

Ich bin ein Fan von Erweiterungsmethoden in C #, hatte aber keinen Erfolg beim Hinzufügen einer Erweiterungsmethode zu einer statischen Klasse wie Console.

Wenn ich beispielsweise der Konsole eine Erweiterung mit dem Namen 'WriteBlueLine' hinzufügen möchte, damit ich gehen kann:

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

Ich habe dies versucht, indem ich eine lokale, öffentliche statische Methode mit Console als 'this'-Parameter hinzugefügt habe ... aber keine Würfel!

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

Dies hat der Konsole keine 'WriteBlueLine'-Methode hinzugefügt ... mache ich das falsch? Oder nach dem Unmöglichen fragen?

Leon Bambrick
quelle
3
Naja. unglücklich, aber ich denke, ich werde durchkommen. Ich bin NOCH eine jungfräuliche Erweiterungsmethode (im Produktionscode sowieso). Vielleicht eines Tages, wenn ich Glück habe.
Andy McCluggage
Ich habe eine Reihe von HtmlHelper-Erweiterungen für ASP.NET MVC geschrieben. Schrieb eine für DateTime, um mir das Ende des angegebenen Datums zu geben (23: 59,59). Hilfreich, wenn Sie den Benutzer bitten, ein Enddatum anzugeben, aber wirklich möchten, dass es das Ende dieses Tages ist.
Tvanfosson
12
Es gibt derzeit keine Möglichkeit, sie hinzuzufügen, da die Funktion in C # nicht vorhanden ist. Nicht weil es per se unmöglich ist , sondern weil die C # -Peeeps sehr beschäftigt sind, hauptsächlich an Erweiterungsmethoden interessiert waren, damit LINQ funktioniert, und nicht genug Nutzen bei statischen Erweiterungsmethoden sahen, um die Zeit zu rechtfertigen, die sie für die Implementierung benötigen würden. Eric Lippert erklärt hier .
Jordan Gray
1
Rufen Sie einfach an Helpers.WriteBlueLine(null, "Hi");:)
Hüseyin Yağlı

Antworten:

285

Nein. Erweiterungsmethoden erfordern eine Instanzvariable (Wert) für ein Objekt. Sie können jedoch einen statischen Wrapper um die ConfigurationManagerSchnittstelle schreiben . Wenn Sie den Wrapper implementieren, benötigen Sie keine Erweiterungsmethode, da Sie die Methode einfach direkt hinzufügen können.

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

      .....

      public static ConfigurationSection GetWidgetSection()
      {
          return GetSection( "widgets" );
      }
 }
Tvanfosson
quelle
8
@Luis - im Kontext wäre die Idee "könnte ich der ConfigurationManager-Klasse eine Erweiterungsmethode hinzufügen, um einen bestimmten Abschnitt zu erhalten?" Sie können einer statischen Klasse keine Erweiterungsmethode hinzufügen, da eine Instanz des Objekts erforderlich ist. Sie können jedoch eine Wrapper-Klasse (oder Fassade) schreiben, die dieselbe Signatur implementiert und den tatsächlichen Aufruf an den realen ConfigurationManager verzögert. Sie können der Wrapper-Klasse eine beliebige Methode hinzufügen, damit es sich nicht um eine Erweiterung handeln muss.
Tvanfosson
Ich finde es hilfreicher, der Klasse, die ConfigurationSection implementiert, einfach eine statische Methode hinzuzufügen. Bei einer Implementierung mit dem Namen MyConfigurationSection würde ich MyConfigurationSection.GetSection () aufrufen, das den bereits eingegebenen Abschnitt zurückgibt, oder null, wenn er nicht vorhanden ist. Das Endergebnis ist das gleiche, es wird jedoch das Hinzufügen einer Klasse vermieden.
Tippen Sie auf
1
@tap - es ist nur ein Beispiel und das erste, das mir in den Sinn kam. Das Prinzip der Einzelverantwortung kommt jedoch ins Spiel. Sollte der "Container" tatsächlich dafür verantwortlich sein, sich selbst aus der Konfigurationsdatei zu interpretieren? Normalerweise habe ich einfach ConfigurationSectionHandler und wandle die Ausgabe von ConfigurationManager in die entsprechende Klasse um und kümmere mich nicht um den Wrapper.
Tvanfosson
5
Für den internen Gebrauch habe ich begonnen, 'X'-Varianten statischer Klassen und Strukturen zum Hinzufügen benutzerdefinierter Erweiterungen zu erstellen:' ConsoleX 'enthält neue statische Methoden für' Console ',' MathX 'enthält neue statische Methoden für' Math ',' ColorX ' erweitert die 'Color'-Methoden usw. Nicht ganz gleich, aber in IntelliSense leicht zu merken und zu entdecken.
user1689175
1
@Xtro Ich stimme zu, es ist schrecklich, aber nicht schlimmer, als nicht in der Lage zu sein, ein Test-Double an seiner Stelle zu verwenden, oder, schlimmer noch, das Testen Ihres Codes aufzugeben, weil statische Klassen es so schwierig machen. Microsoft scheint mir zuzustimmen, weil sie aus diesem Grund die Klassen HttpContextWrapper / HttpContextBase eingeführt haben, um den statischen HttpContext.Current für MVC zu umgehen.
Tvanfosson
91

Können Sie Klassen in C # statische Erweiterungen hinzufügen? Nein, aber Sie können dies tun:

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

So funktioniert das. Obwohl Sie technisch keine statischen Erweiterungsmethoden schreiben können, nutzt dieser Code eine Lücke in den Erweiterungsmethoden. Diese Lücke besteht darin, dass Sie Erweiterungsmethoden für Nullobjekte aufrufen können, ohne die Nullausnahme zu erhalten (es sei denn, Sie greifen über @this auf etwas zu).

So würden Sie dies verwenden:

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

WARUM habe ich den Standardkonstruktor als Beispiel aufgerufen und UND warum gebe ich nicht einfach neues T () im ersten Codeausschnitt zurück, ohne den gesamten Ausdrucksmüll zu verursachen? Nun, heute ist dein Glückstag, denn du bekommst einen 2fer. Wie jeder fortgeschrittene .NET-Entwickler weiß, ist new T () langsam, da es einen Aufruf von System.Activator generiert, der Reflection verwendet, um den Standardkonstruktor vor dem Aufruf abzurufen. Verdammt Microsoft! Mein Code ruft jedoch den Standardkonstruktor des Objekts direkt auf.

Statische Erweiterungen wären besser als diese, aber verzweifelte Zeiten erfordern verzweifelte Maßnahmen.

Herr widerlich
quelle
2
Ich denke, für Dataset wird dieser Trick funktionieren, aber ich bezweifle, dass er für die Konsolenklasse funktioniert, da die Konsole eine statische Klasse ist und statische Typen nicht als Argumente verwendet werden können :)
ThomasBecker
Ja, ich wollte dasselbe sagen. Dies sind pseudostatische Erweiterungsmethoden für eine nicht statische Klasse. Das OP war eine Erweiterungsmethode für eine statische Klasse.
Mark A. Donohoe
2
Es ist viel besser und einfacher, nur für solche Methoden eine Namenskonvention hat wie XConsole, ConsoleHelperund so weiter.
Alex Zhukovskiy
9
Dies ist ein faszinierender Trick, aber das Ergebnis stinkt. Sie erstellen ein Nullobjekt und scheinen dann eine Methode darauf aufzurufen - obwohl jahrelang gesagt wurde, dass "das Aufrufen einer Methode für ein Nullobjekt eine Ausnahme verursacht". Es funktioniert, aber ... ach ... verwirrend für alle, die später warten. Ich werde nicht abstimmen, weil Sie dem Informationspool hinzugefügt haben, was möglich ist. Aber ich hoffe aufrichtig, dass niemand diese Technik jemals anwendet !! Zusätzliche Beschwerde: Übergeben Sie keine davon an eine Methode und erwarten Sie eine OO-Unterklasse: Die aufgerufene Methode ist der Typ der Parameterdeklaration und nicht der Typ des übergebenen Parameters .
ToolmakerSteve
5
Das ist schwierig, aber ich mag es. Eine Alternative dazu (null as DataSet).Create();könnte sein default(DataSet).Create();.
Bagerfahrer
54

Es ist nicht möglich.

Und ja, ich denke, MS hat hier einen Fehler gemacht.

Ihre Entscheidung ist nicht sinnvoll und zwingt Programmierer, (wie oben beschrieben) eine sinnlose Wrapper-Klasse zu schreiben.

Hier ein gutes Beispiel: Versuch, die statische MS Unit-Testklasse zu erweitern Assert: Ich möchte 1 weitere Assert-Methode AreEqual(x1,x2) .

Die einzige Möglichkeit, dies zu tun, besteht darin, auf verschiedene Klassen zu verweisen oder einen Wrapper mit etwa 100 verschiedenen Assert-Methoden zu schreiben. Warum!?

Wenn die Entscheidung getroffen wurde, Erweiterungen von Instanzen zuzulassen, sehe ich keinen logischen Grund, statische Erweiterungen nicht zuzulassen. Die Argumente zum Schneiden von Bibliotheken stehen nicht zur Verfügung, sobald Instanzen erweitert werden können.

Tom Deloford
quelle
20
Ich habe auch versucht, die MS Unit Test-Klasse Assert um Assert.Throws und Assert.DoesNotThrow zu erweitern, und hatte das gleiche Problem.
Stefano Ricciardi
3
Ja, ich auch :( Ich dachte, ich kann Assert.Throwsmit der Antwort stackoverflow.com/questions/113395/…
CallMeLaNN
27

Ich bin auf diesen Thread gestoßen, als ich versucht habe, eine Antwort auf dieselbe Frage zu finden, die das OP hatte. Ich habe nicht die Antwort gefunden, die ich wollte, aber am Ende habe ich das getan.

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

Und ich benutze es so:

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

Vielleicht könnten Sie eine statische Klasse mit Ihrem benutzerdefinierten Namespace und demselben Klassennamen hinzufügen:

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);
        }
    }
}
Pag Sun.
quelle
1
Dies löst jedoch nicht das Problem, dass jede einzelne Methode aus der ursprünglichen statischen Klasse, die Sie in Ihrem Wrapper behalten möchten, neu implementiert werden muss . Es ist immer noch ein Wrapper, obwohl es den Vorteil hat, dass weniger Änderungen im Code erforderlich sind, der es verwendet…
Binki
11

Nee. Für Definitionen von Erweiterungsmethoden ist eine Instanz des Typs erforderlich, den Sie erweitern. Es ist unglücklich; Ich bin nicht sicher, warum es erforderlich ist ...


quelle
4
Dies liegt daran, dass eine Erweiterungsmethode verwendet wird, um eine Instanz eines Objekts zu erweitern. Wenn sie das nicht tun würden, wären sie nur normale statische Methoden.
Derek Ekins
31
Es wäre schön, beides zu tun, nicht wahr?
7

Bei Erweiterungsmethoden sind die Erweiterungsmethoden selbst statisch. Sie werden jedoch so aufgerufen, als wären sie Instanzmethoden. Da eine statische Klasse nicht instanziierbar ist, hätten Sie niemals eine Instanz der Klasse, von der aus Sie eine Erweiterungsmethode aufrufen könnten. Aus diesem Grund erlaubt der Compiler nicht, Erweiterungsmethoden für statische Klassen zu definieren.

Herr Obnoxious schrieb: "Wie jeder fortgeschrittene .NET-Entwickler weiß, ist new T () langsam, da es einen Aufruf von System.Activator generiert, der Reflection verwendet, um den Standardkonstruktor vor dem Aufruf abzurufen."

New () wird in den IL-Befehl "newobj" kompiliert, wenn der Typ zur Kompilierungszeit bekannt ist. Newobj verwendet einen Konstruktor für den direkten Aufruf. Aufrufe von System.Activator.CreateInstance () werden mit der IL-Anweisung "call" kompiliert, um System.Activator.CreateInstance () aufzurufen. New () führt bei Verwendung für generische Typen zu einem Aufruf von System.Activator.CreateInstance (). Der Beitrag von Mr. Obnoxious war in diesem Punkt unklar ... und nun, widerlich.

Dieser Code:

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

produziert diese 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
Brian Griffin
quelle
5

Sie können einem Typ keine statischen Methoden hinzufügen . Sie können einer Instanz eines Typs nur (Pseudo-) Instanzmethoden hinzufügen.

Der thisModifikator soll den C # -Compiler anweisen, die Instanz auf der linken Seite des zu übergeben. als ersten Parameter der statischen / Erweiterungsmethode zu übergeben.

Beim Hinzufügen statischer Methoden zu einem Typ muss keine Instanz für den ersten Parameter übergeben werden.

Brannon
quelle
4

Ich habe versucht, dies mit System.Environment zu tun, als ich Erweiterungsmethoden lernte und nicht erfolgreich war. Der Grund ist, wie andere erwähnen, dass Erweiterungsmethoden eine Instanz der Klasse erfordern.

Robert S.
quelle
3

Es ist nicht möglich, eine Erweiterungsmethode zu schreiben, es ist jedoch möglich, das gewünschte Verhalten nachzuahmen.

using FooConsole = System.Console;

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

Auf diese Weise können Sie Console.WriteBlueLine (fooText) in anderen Klassen aufrufen. Wenn die anderen Klassen Zugriff auf die anderen statischen Funktionen von Console wünschen, müssen sie über ihren Namespace explizit referenziert werden.

Sie können der Ersetzungsklasse jederzeit alle Methoden hinzufügen, wenn Sie alle an einem Ort haben möchten.

Sie hätten also so etwas wie

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

Dies würde die Art von Verhalten liefern, nach der Sie suchen.

* Hinweis Die Konsole muss über den Namespace hinzugefügt werden, in den Sie sie eingefügt haben.

Douglas Potesta
quelle
1

ja, in einem begrenzten Sinne.

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

Dies funktioniert, die Konsole jedoch nicht, da sie statisch ist.

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

Dies funktioniert, weil es sich nicht im selben Namespace befindet. Das Problem ist, dass Sie für jede Methode von System.Console eine statische Proxy-Methode schreiben müssen. Es ist nicht unbedingt eine schlechte Sache, da Sie so etwas hinzufügen können:

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

oder

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

Die Art und Weise, wie es funktioniert, ist, dass Sie etwas in die Standard-WriteLine einbinden. Es könnte eine Zeilenanzahl oder ein Filter für schlechte Wörter oder was auch immer sein. Wenn Sie in Ihrem Namespace nur "Konsole" angeben, z. B. "WebProject1", und den Namespace "System" importieren, wird "WebProject1.Console" für diese Klassen im Namespace "WebProject1" anstelle von "System.Console" ausgewählt. Dieser Code verwandelt also alle Console.WriteLine-Aufrufe in Blau, sofern Sie System.Console.WriteLine nie angegeben haben.

Schwarzer Hund
quelle
Leider funktioniert der Ansatz, einen Nachkommen zu verwenden, nicht, wenn die Basisklasse versiegelt ist (wie viele in der .NET-Klassenbibliothek)
George Birbilis
1

Folgendes wurde als abgelehnt bearbeiten zu tvanfosson Antwort. Ich wurde gebeten, es als meine eigene Antwort beizutragen. Ich habe seinen Vorschlag verwendet und die Implementierung eines ConfigurationManagerWrappers abgeschlossen. Im Prinzip habe ich einfach ...die Antwort von in tvanfosson ausgefüllt .

Nein. Erweiterungsmethoden erfordern eine Instanz eines Objekts. Sie können jedoch einen statischen Wrapper um die ConfigurationManager-Oberfläche schreiben. Wenn Sie den Wrapper implementieren, benötigen Sie keine Erweiterungsmethode, da Sie die Methode einfach direkt hinzufügen können.

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);
    }
}
André C. Andersen
quelle
0

Sie können eine Umwandlung in null verwenden, damit es funktioniert.

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

Die Erweiterung:

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

Dein Typ:

public class YourType { }
Wouter
quelle
-4

Sie können dies tun, wenn Sie bereit sind, es ein wenig zu "friggen", indem Sie eine Variable der statischen Klasse erstellen und sie null zuweisen. Diese Methode ist jedoch für statische Aufrufe der Klasse nicht verfügbar. Sie sind sich also nicht sicher, wie häufig sie verwendet wird:

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();
    }
}
Tenaka
quelle
Genau das habe ich getan. Meine Klasse heißt MyTrace :)
Gishu
Nützlicher Tipp. ein bisschen nach Code riechen, aber ich denke, wir könnten das Null-Objekt in einer Basisklasse oder so etwas verstecken. Vielen Dank.
Tom Deloford
1
Ich kann diesen Code nicht kompilieren. Fehler 'System.Console': Statische Typen können nicht als Parameter verwendet werden
kuncevic.dev
Ja, das geht nicht. Verdammt, ich dachte du wärst auf etwas! Statische Typen können nicht als Parameter an Methoden übergeben werden, was meiner Meinung nach sinnvoll ist. Hoffen wir nur, dass MS das Holz von den Bäumen auf diesem sieht und es ändert.
Tom Deloford
3
Ich hätte versuchen sollen, meinen eigenen Code zu kompilieren! Wie Tom sagt, funktioniert dies nicht mit statischen Klassen.
Tenaka