ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

那好吧。不幸的是,但我想我会过得去的。我仍然是一种扩展方法处女(无论如何在生产代码中)。也许有一天,如果我幸运的话。
我为 ASP.NET MVC 编写了许多 HtmlHelper 扩展。为 DateTime 写了一个给我给定日期的结束 (23:59.59)。当您要求用户指定结束日期但真的希望它是当天的结束时很有帮助。
目前无法添加它们,因为 C# 中不存在该功能。不是因为它本身是不可能的,而是因为 C# 窥视者非常忙碌,他们最感兴趣的是使 LINQ 工作的扩展方法,并且没有看到静态扩展方法有足够的好处来证明他们的时间是合理的采取实施。 Eric Lippert explains here
只需致电 Helpers.WriteBlueLine(null, "Hi"); :)

t
tvanfosson

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

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

      .....

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

@Luis - 在上下文中,这个想法是“我可以向 ConfigurationManager 类添加一个扩展方法来获取特定部分吗?”您不能将扩展方法添加到静态类,因为它需要对象的实例,但您可以编写实现相同签名并将实际调用推迟到实际 ConfigurationManager 的包装类(或外观)。您可以将所需的任何方法添加到包装类,因此它不需要是扩展。
我发现向实现 ConfigurationSection 的类添加一个静态方法更有帮助。因此,给定一个名为 MyConfigurationSection 的实现,我将调用 MyConfigurationSection.GetSection(),它返回已键入的部分,如果不存在则返回 null。最终结果是相同的,但它避免了添加一个类。
@tap - 这只是一个例子,也是我想到的第一个例子。不过,单一责任原则开始发挥作用。 “容器”实际上应该负责从配置文件中解释自己吗?通常我只是有 ConfigurationSectionHandler 并将 ConfigurationManager 的输出转换为适当的类,而不用担心包装器。
对于内部使用,我开始创建静态类和结构的“X”变体以添加自定义扩展:“ConsoleX”包含“Console”的新静态方法,“MathX”包含“Math”的新静态方法,“ColorX”扩展了“颜色”方法等。不太一样,但在 IntelliSense 中很容易记住和发现。
@Xtro 我同意这很糟糕,但并不比无法在其位置使用测试替身更糟糕,或者更糟糕的是,放弃测试您的代码,因为静态类使它变得如此困难。 Microsoft 似乎同意我的观点,因为这是他们引入 HttpContextWrapper/HttpContextBase 类来绕过 MVC 的静态 HttpContext.Current 的原因。
C
Carlo V. Dango

你可以在 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; }
}

这是它的工作原理。虽然您不能在技术上编写静态扩展方法,但此代码利用了扩展方法中的漏洞。这个漏洞是你可以在 null 对象上调用扩展方法而不会得到 null 异常(除非你通过 @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(...)

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

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


我认为对于 Dataset 这个技巧会起作用,但我怀疑它是否适用于 Console 类,因为 Console 是静态类,静态类型不能用作参数:)
为诸如 XConsoleConsoleHelper 等方法设置一些命名约定会更好、更容易。
这是一个迷人的技巧,但结果很臭。您创建了一个空对象,然后似乎在其上调用了一个方法——尽管多年来一直被告知“在空对象上调用一个方法会导致异常”。它可以工作,但是..ugh ...让以后维护的任何人感到困惑。我不会投反对票,因为您已经在信息库中添加了关于什么是可能的信息。但我真诚地希望没有人使用这种技术!额外的抱怨:不要将其中之一传递给方法,并期望获得 OO 子类化:调用的方法将是参数声明的类型,而不是传入的参数类型。
这很棘手,但我喜欢它。 (null as DataSet).Create(); 的一种替代方法可能是 default(DataSet).Create();
不明白为什么这可以收到多达 93 票?由于花哨的通用和基于反射的代码,这并不能解决与问题相关的任何问题。
P
Pang

这是不可能的。

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

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

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

做到这一点的唯一方法是指向不同的类或编写一个包含 100 多个不同 Assert 方法的包装器。为什么!?

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


我还试图扩展 MS 单元测试类 Assert 以添加 Assert.Throws 和 Assert.DoesNotThrow 并面临同样的问题。
是的,我也是 :( 我想我可以对答案 stackoverflow.com/questions/113395/…Assert.Throws
这篇文章今天仍然像 10 多年前一样无关紧要,使用其他方法扩展 Static 类的净收益为零。一开始它总是似乎是一个好主意,但在实践中有太多理由说明为什么这是一个反模式。根本没有 pointless 包装类,而是有一个非常有意义且专门构建的实用程序或帮助类,可以将所有自定义逻辑保存在一个地方。不要试图复制 Assert 上的所有函数,只对您的自定义函数进行编码,开发人员在需要时调用您的自定义逻辑,其余部分使用 Assert。
错误是在这里使用的错误词。记住 Eric 的不朽名言:功能默认未实现; C# 没有特性,因为没有人设计、指定、实现、测试、记录和发布该特性。每个功能都有成本,它与优先级有关。
现有的扩展类提供更短和更清晰的调用,并被编译成简单的扩展静态方法调用。运行时没有成本。代码更具可读性。由于类的名称,代码不更具可读性。由于专注于目标并跳过将对象作为参数传递,因此更具可读性。少写。如果您扩展静态类,则没有可读性优势。您只需更改会降低可读性并且实际上是成本的类的名称。为什么我们想要它们?因为我们很懒,不想发明新的有意义的名字。
A
Adel G.Eibesh

我在试图找到 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à");

从长远来看,这会将相关功能分散在一系列不相关的参数类型中。很难提供文档和维护。
P
Pag Sun

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

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

但这并不能解决需要从要保留在包装器中的原始静态类中重新实现每个方法的问题。它仍然是一个包装器,尽管它确实具有在使用它的代码中需要更少更改的优点……
m
mbx

从 C#7 开始,不支持此功能。但是有 discussions about integrating something like that in C#8proposals worth supporting


P
Pang

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


这是因为扩展方法用于扩展对象的实例。如果他们不这样做,他们将只是常规的静态方法。
两者都做会很好,不是吗?
B
Brannon

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

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

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


这种答案虽然在技术上是正确的,但并没有提供太多有用的东西。这就像是在问“为什么汽车有四个轮子?”有人回答说“他们有 4 辆,因为否则如果他们有 3 辆,例如,他们将是三轮车”,这在技术上几乎不正确,因为这只是关于问题的琐事,但跳过了问题的实际内容,为什么'这不是以任何其他允许这样做的方式实现的,如果这还不能以其他方式实现。
B
Brian Griffin

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

Obnoxious 先生写道:“任何高级 .NET 开发人员都知道,new T() 很慢,因为它会生成对 System.Activator 的调用,该调用使用反射来获取默认构造函数,然后再调用它”。

如果类型在编译时已知,New() 将编译为 IL“newobj”指令。 Newobj 采用构造函数进行直接调用。对 System.Activator.CreateInstance() 的调用编译为 IL“调用”指令以调用 System.Activator.CreateInstance()。对泛型类型使用 New() 将导致调用 System.Activator.CreateInstance()。 Obnoxious 先生的帖子在这一点上不清楚……而且,令人讨厌。

这段代码:

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

R
Robert S.

当我学习扩展方法并且没有成功时,我尝试使用 System.Environment 执行此操作。正如其他人提到的那样,原因是因为扩展方法需要类的实例。


D
Douglas Potesta

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

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)。如果其他类想要访问 Console 的其他静态函数,则必须通过它们的命名空间显式引用它们。

如果您想将所有方法都放在一个地方,您可以随时将所有方法添加到替换类中。

所以你会有类似的东西

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

这将提供您正在寻找的那种行为。

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


B
Black Dog

是的,在有限的意义上。

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 中。它可能是行数或坏词过滤器或其他任何东西。每当您在命名空间中指定 Console 时说 WebProject1 并导入命名空间 System,WebProject1.Console 将选择 System.Console 作为命名空间 WebProject1 中这些类的默认值。因此,只要您从未指定 System.Console.WriteLine,此代码会将所有 Console.WriteLine 调用变为蓝色。


不幸的是,当基类被密封时,使用后代的方法不起作用(就像 .NET 类库中的许多一样)
C
Community

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

不可以。扩展方法需要一个对象的实例。但是,您可以围绕 ConfigurationManager 接口编写一个静态包装器。如果您实现包装器,则不需要扩展方法,因为您可以直接添加方法。

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

W
Wouter

您可以在 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 { }

C
Clark Kent

不幸的是不,你不能扩展静态类

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

G
Gourav

用这个

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

  .....

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

A
Amal K

虽然 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");

T
Tenaka

如果您愿意通过创建静态类的变量并将其分配给 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();
    }
}

这正是我所做的。我的课叫做 MyTrace :)
有用的提示。有点代码味道,但我想我们可以将空对象隐藏在基类或其他东西中。谢谢。
我无法编译此代码。错误“System.Console”:静态类型不能用作参数
是的,这是无法做到的。该死的,我还以为你在那里搞事情呢!静态类型不能作为参数传递给我认为有意义的方法。让我们只希望MS看到这棵树上的木头并改变它。
我应该尝试编译自己的代码!正如汤姆所说,这不适用于静态类。