我可以向现有静态类添加扩展方法吗?

本文关键字:扩展 方法 添加 静态类 可以向 我可以 | 更新日期: 2023-09-27 17:47:46

我是 C# 中扩展方法的粉丝,但尚未成功地将扩展方法添加到静态类,例如 Console .

例如,如果我想添加一个扩展名 Console ,称为" WriteBlueLine ",这样我就可以去:

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

我通过添加一个本地的公共静态方法来尝试这样做,Console作为"this"参数......但没有骰子!

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

这没有添加" WriteBlueLine "方法Console...我做错了吗?还是要求不可能的事情?

我可以向现有静态类添加扩展方法吗?

No. 扩展方法需要对象的实例变量(值(。 但是,您可以在ConfigurationManager接口周围编写静态包装器。 如果实现包装器,则不需要扩展方法,因为可以直接添加方法。

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

你能在 C# 中为类添加静态扩展吗?不,但你可以这样做:

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

这是它的工作原理。 虽然在技术上无法编写静态扩展方法,但此代码利用了扩展方法中的漏洞。 这个漏洞是,您可以在空对象上调用扩展方法而不会获得空异常(除非您通过@this访问任何内容(。

因此,这是您将如何使用它:

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

现在为什么我选择调用默认构造函数作为示例,为什么我不在第一个代码片段中返回新的 T((,而不做所有这些表达式垃圾?好吧,今天是你的幸运日,因为你得到了一个2fer。 正如任何高级 .NET 开发人员所知,新的 T(( 很慢,因为它生成了对 System.Activator 的调用,该调用在调用之前使用反射来获取默认构造函数。 该死的Microsoft!但是,我的代码直接调用对象的默认构造函数。

静态扩展会比这更好,但绝望的时刻需要绝望的措施。

是不可能的。

是的,我认为MS在这里犯了一个错误。

他们的决定没有意义,并迫使程序员编写(如上所述(一个毫无意义的包装类。

这是一个很好的例子:尝试扩展静态 MS 单元测试类断言:我想要 1 个 断言方法 AreEqual(x1,x2) .

执行此操作的唯一方法是指向不同的类或围绕 100 多个不同的 Assert 方法编写包装器。为什么!?

如果决定允许实例扩展,我认为没有合乎逻辑的理由不允许静态扩展。一旦实例可以扩展,关于切片库的争论就站不住脚了。

我在试图找到 OP 相同问题的答案时偶然发现了这个线程。我没有找到我想要的答案,但我最终做到了。

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

我是这样使用它的:

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

从 C#7 开始,这不受支持。然而,有关于在 C#8 中集成类似内容的讨论和值得支持的提案。

也许您可以添加一个具有自定义命名空间和相同类名的静态类:

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

不。 扩展方法定义需要要扩展的类型的实例。 这很不幸;我不确定为什么需要它...

不能向类型添加静态方法。 只能将(伪(实例方法添加到类型的实例。

this修饰符的要点是告诉 C# 编译器将.左侧的实例作为静态/扩展方法的第一个参数传递。

在向类型添加静态方法的情况下,第一个参数没有要传递的实例。

至于扩展方法,扩展方法本身是静态的;但它们被调用,就好像它们是实例方法一样。 由于静态类不可实例化,因此永远不会有从中调用扩展方法的类实例。 因此,编译器不允许为静态类定义扩展方法。

Obnoxious先生写道:"正如任何高级.NET开发人员都知道的那样,新的T((很慢,因为它生成了对System.Activator的调用,该调用在调用之前使用反射来获取默认构造函数。

New(( 编译为 IL "newobj" 指令,如果类型在编译时已知。 Newobj 采用构造函数进行直接调用。 对 System.Activator.CreateInstance(( 的调用编译为 IL "call" 指令以调用 System.Activator.CreateInstance((。 New(( 用于泛型类型时将导致对 System.Activator.CreateInstance(( 的调用。 奥布诺西斯先生的帖子在这一点上并不清楚......好吧,令人讨厌。

此代码:

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

生成此 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

当我学习扩展方法时,我试图用System.Environment来做到这一点,但没有成功。正如其他人提到的,原因是扩展方法需要类的实例。

尽管Console的方法是静态的,但其静态方法Write()WriteLine()只是分别将调用重定向到 Console.Out.Write()Console.Out.WriteLine()Out 是一个实例,其类型派生自抽象类 TextWriter 。这使得可以为TextWriter定义扩展方法:

public static class ConsoleTextWriterExtensions
{
    public static void WriteBlueLine(this TextWriter writer, string text)
    {
        Console.ForegroundColor = ConsoleColor.Blue;
        writer.WriteLine(text);
        Console.ResetColor();
    }
    public static void WriteUppercase(this TextWriter writer, string text)
    {
        writer.Write(text.ToUpper());
    }
}

然后可以像这样调用该方法:

Console.Out.WriteBlueLine();

最好的部分是标准错误流Console.Error实例的类型也派生自TextWriter,这使得相同的扩展方法也可用于Console.Error

Console.Error.WriteBlueLine();

如果您定义了像 WriteTable() 这样的扩展方法(用于将表写入控制台(,这会非常有用,因为您也可以将其用于错误流或任何其他TextWriter对象。

较新版本的 C# 允许此时间更短,Consoleusing static 语句使Console.前缀变为红色:

using static System.Console;
Out.WriteBlueLine("A blue line");
Error.WriteBlueLine("A blue line");
无法

编写扩展方法,但可以模仿您要求的行为。

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

这将允许您在其他类中调用 Console.WriteBlueLine(fooText(。如果其他类想要访问控制台的其他静态函数,则必须通过其命名空间显式引用它们。

如果要将所有方法都

放在一个位置,则始终可以将所有方法添加到替换类中。

所以你会有类似的东西

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

这将提供您正在寻找的行为类型。

*注意 控制台必须通过您放入的命名空间添加。

以下内容作为对tvanfosson答案的编辑被拒绝。我被要求贡献它作为我自己的答案。我使用了他的建议并完成了ConfigurationManager包装器的实现。原则上,我只是在tvanfosson的回答中填写了...

不。扩展方法需要对象的实例。您可以 但是,在配置管理器周围编写静态包装器 接口。如果实现包装器,则不需要扩展 方法,因为您可以直接添加方法。

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

是的,在有限的意义上。

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

这有效,但控制台不起作用,因为它是静态的。

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

这是有效的,因为只要它不在同一命名空间上。问题是您必须为 System.Console 的每个方法编写一个代理静态方法。这不一定是一件坏事,因为您可以添加这样的东西:

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

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

它的工作方式是将某些内容连接到标准WriteLine中。它可能是行数或坏词过滤器或其他什么。每当您在命名空间中指定控制台(例如 WebProject1 并导入命名空间 System(时,WebProject1.Console 将优先于 System.Console 作为命名空间 WebProject1 中这些类的默认选项。因此,此代码会将所有 Console.WriteLine 调用转换为蓝色,只要您从未指定 System.Console.WriteLine。

您可以在 null 上使用强制转换来使其工作。

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

扩展名:

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

您的类型:

public class YourType { }

不,你不能扩展静态类

https://onecompiler.com/csharp/3xvbe7axg

using System;
namespace HelloWorld
{
  public static class console_extensions {
    public static void EXTENSION(this object item) {
      System.Console.WriteLine("HELLO THERE!");
    }
  }
  
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
            Console.EXTENSION();
            ((Console)null).EXTENSION();
            Console l = new Console();
            l.EXTENSION();
        }
    }
}

输出

Compilation failed: 4 error(s), 0 warnings
HelloWorld.cs(16,12): error CS0117: `System.Console' does not contain a definition for `EXTENSION'
/usr/lib/mono/4.5/mscorlib.dll (Location of the symbol related to previous error)
HelloWorld.cs(17,5): error CS0716: Cannot convert to static type `System.Console'
HelloWorld.cs(18,4): error CS0723: `l': cannot declare variables of static types
/usr/lib/mono/4.5/mscorlib.dll (Location of the symbol related to previous error)
HelloWorld.cs(18,16): error CS0712: Cannot create an instance of the static class `System.Console'
/usr/lib/mono/4.5/mscorlib.dll (Location of the symbol related to previous error)

但是您可以将null传递给扩展方法

using System;
namespace HelloWorld
{
  public static class static_extensions {
      public static void print(this object item, int data = 0) {
      Console.WriteLine("EXT: I AM A STATIC EXTENSION!");
      Console.WriteLine("EXT: MY ITEM IS: " + item);
      Console.WriteLine("EXT: MY DATA IS: " + data);
      string i;
      if (item == null) {
        i = "null";
      } else {
        i = item.GetType().Name;
      }
      Console.WriteLine("EXT: MY TYPE IS: " + i + "'n");
    }
  }
    public class Program
    {
    
        public static void Main(string[] args)
        {
          // an extension method can be
          //   called directly
          //  (null is an instance)
          static_extensions.print(null);
          // an extension method can also be
          //   called directly with arguments
          //  (null is an instance)
          static_extensions.print(null, 1);
          
          // an extension method can also be
          //   called as part of an instance
          int x = 0; // initialize int
          x.print();
          
          // an extension method can also be
          //   called as part of an instance
          //   and with data
          int x2 = 0; // initialize int
          x2.print(2);
          
          // an extension method can also be
          //   called directly from null
          //   since `null` is an instance
          ((string)null).print();
          
          // an extension method can also be
          //   called directly from null
          //   and with data
          //   since `null` is an instance
          ((string)null).print(4);
        }
    }
}

现场示例:https://onecompiler.com/csharp/3xvbc8s6w

输出:

EXT: I AM A STATIC EXTENSION!
EXT: MY ITEM IS: 
EXT: MY DATA IS: 0
EXT: MY TYPE IS: null
EXT: I AM A STATIC EXTENSION!
EXT: MY ITEM IS: 
EXT: MY DATA IS: 1
EXT: MY TYPE IS: null
EXT: I AM A STATIC EXTENSION!
EXT: MY ITEM IS: 0
EXT: MY DATA IS: 0
EXT: MY TYPE IS: Int32
EXT: I AM A STATIC EXTENSION!
EXT: MY ITEM IS: 0
EXT: MY DATA IS: 2
EXT: MY TYPE IS: Int32
EXT: I AM A STATIC EXTENSION!
EXT: MY ITEM IS: 
EXT: MY DATA IS: 0
EXT: MY TYPE IS: null
EXT: I AM A STATIC EXTENSION!
EXT: MY ITEM IS: 
EXT: MY DATA IS: 4
EXT: MY TYPE IS: null

我真的不明白人们认为他们从能够扩展静态类中获得什么......

你只是做这样的事情,到底会牺牲什么?

public static class MyConsole
{
    public static void WriteBlueLine(string text)
    {
        Console.ForegroundColor = ConsoleColor.Blue;
        Console.WriteLine(text);
        Console.ResetColor();
    }
}
//...
MyConsole.WriteBlueLine("I'm so blue...");
Console.WriteLine("...and I'm not.");

这是最少的额外打字工作,作为奖励,它使事情保持透明......

毕竟,即使是常规的扩展方法也只是帮助程序方法的简写。它不允许你对类(实例(做任何你无法从常规方法做的事情。

如果您

愿意通过创建静态类的变量并将其分配给 null 来"冷却"它,则可以执行此操作。但是,此方法不适用于类上的静态调用,因此不确定它的用途:

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