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

Leo*_*ick 519 c# extension-methods static

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

例如,如果我想向Console添加一个名为'WriteBlueLine'的扩展,那么我可以去:

Console.WriteBlueLine("This text is blue");
Run Code Online (Sandbox Code Playgroud)

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

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

这没有向Console添加'WriteBlueLine'方法......我做错了吗?或者要求不可能?

tva*_*son 272

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

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

      .....

      public static ConfigurationSection GetWidgetSection()
      {
          return GetSection( "widgets" );
      }
 }
Run Code Online (Sandbox Code Playgroud)

  • @Luis - 在上下文中,我的想法是"我可以在ConfigurationManager类中添加一个扩展方法来获取特定的部分吗?" 您不能将扩展方法添加到静态类,因为它需要对象的实例,但您可以编写实现相同签名的包装类(或外观),并将实际调用推迟到真实的ConfigurationManager.您可以向包装类添加任何您想要的方法,因此它不需要是扩展名. (8认同)
  • 对于内部使用,我开始创建用于添加自定义扩展的静态类和结构的"X"变体:'ConsoleX'包含用于'Console'的新静态方法,'MathX'包含用于'Math','ColorX'的新静态方法扩展'颜色'方法等不一样,但易于在IntelliSense中记忆和发现. (4认同)

小智 90

你能在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; }
}
Run Code Online (Sandbox Code Playgroud)

这是它的工作原理.虽然您无法在技术上编写静态扩展方法,但此代码利用了扩展方法中的漏洞.这个漏洞是你可以调用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(...)
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这是一个引人入胜的伎俩,但结果很臭.你创建一个空对象,然后似乎在它上面调用一个方法 - 尽管多年被告知"在空对象上调用一个方法会导致异常".它有效,但是......很久以后对任何人都感到困惑.我不会投票,因为你已经添加了关于可能的信息池.但我真诚地希望没有人使用过这种技术!! 附加投诉:不要将其中一个传递给方法,并期望得到OO子类:调用的方法将是*参数声明的类型*不传入*参数*的类型. (8认同)
  • 不明白为什么这个可以得到高达 93 的赞成票?由于花哨的通用和基于反射的代码,这并不能解决与问题相关的任何问题。 (5认同)
  • 这很棘手,但我喜欢。(null为DataSet).Create();的一种替代方法可以是default(DataSet).Create();。 (4认同)
  • 说得好@Hopeless,整篇文章充满了糟糕的支持论据,静态类扩展的大多数拥护者都来自其他语言或开发范例,因此根本不明白如何以本机方式实现他们的要求。但是,这篇文章甚至没有解决 OP 问题时却获得了这么多选票,我永远无法理解。 (3认同)
  • 我认为对于Dataset这个技巧会起作用,但是我怀疑它适用于Console类,因为Console是静态类,静态类型不能用作参数:) (2认同)
  • 对于诸如`XConsole`,`ConsoleHelper`之类的方法,有一些命名约定要好得多,也更容易. (2认同)

Tom*_*ord 51

这是不可能的.

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

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

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

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

如果决定允许扩展实例,我认为没有合理的理由不允许静态扩展.一旦可以扩展实例,关于分割库的论点就不会出现.

  • 我还试图扩展MS Unit Test类Assert以添加Assert.Throws和Assert.DoesNotThrow并面临同样的问题. (19认同)
  • 这篇文章在今天仍然像十多年前一样无关紧要,使用附加方法扩展“静态”类的净收益为零。一开始它总是_似乎_是一个好主意,但在实践中有太多原因说明为什么这是一个_反模式_。根本没有无意义的包装类,而是有一个非常有意义且专门构建的实用程序或帮助程序类,可以将所有自定义逻辑保存在一个位置。不要尝试复制“Assert”上的所有函数,仅编写您的自定义函数,开发人员在需要时调用您的自定义逻辑,其余的使用 Assert。 (5认同)
  • 是的我也是:(我以为我可以做'Assert.Throws`来回答http://stackoverflow.com/questions/113395/how-can-i-test-for-an-expected-exception-with-a-特定异常信息从 - 一个 (3认同)
  • 错误是这里使用的错误词。记住 Eric 的不朽名言:_功能默认未实现;C# 没有某个功能,因为没有人设计、指定、实现、测试、记录和交付该功能。每个功能都有成本,它与优先级有关。 (3认同)
  • 现有的扩展类提供更短、更清晰的调用,并被编译为简单的扩展静态方法调用。运行时无成本。代码更具可读性。由于类的名称,代码的可读性并不高。由于专注于目标并跳过将对象作为参数传递,因此它更具可读性。少写一点。如果扩展静态类,则不会有任何可读性优势。您只需更改类的名称,这会降低可读性,而且实际上是一种成本。为什么我们想要它们?因为我们很懒,不想发明新的有意义的名字。 (2认同)
  • @ChrisSchaller 我发现这是一个非常糟糕的做法。您想要扩展静态类的原因有很多。例如,在 Unity 中,“Debug”类是静态的。如果您希望向“Debug.Log()”方法添加一些额外的功能,例如:添加详细过滤器,这样您就不需要删除一些可能会扰乱控制台但又很混乱的日志消息,该怎么办?对于调试仍然有用。现在你必须用包装纸凑合。 (2认同)
  • @OliverGiesen 扩展静态类的目的是避免在人类必须记住的命名空间中创建更多符号名称。Debug.Log(extra_params) 是一个比 DebugHelper.Log() 更好的函数,因为现在有人必须记住“DebugHelper”,而每个人都记住“Debug”,因为他们一直使用它。其他静态类也是如此。我已经记不清有多少次这个功能会非常有用并且会显着简化代码。如果使用不当,任何事物都可能成为反模式。 (2认同)

小智 23

在试图寻找OP所遇到的同一问题的答案时,我偶然发现了这个问题.我找不到我想要的答案,但我最终还是这样做了.

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

我这样使用它:

ConsoleColor.Cyan.WriteLine("voilà");
Run Code Online (Sandbox Code Playgroud)

  • 从长远来看,这会将相关功能分散到一系列不相关的参数类型中。提供文档和维护将很困难。 (4认同)

Pag*_*Sun 17

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

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 但这并不能解决需要从要保留在包装器中的原始静态类中重新实现*每个*单个方法的问题。它仍然是一个包装器,尽管它确实具有需要对使用它的代码进行较少更改的优点...... (3认同)

小智 11

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

  • 做两件事都很好,不是吗? (29认同)
  • 这是因为扩展方法用于扩展对象的实例.如果他们不这样做,他们只会是常规的静态方法. (4认同)

Bri*_*fin 7

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

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

如果在编译时已知类型,则将New()编译为IL"newobj"指令.Newobj采用构造函数进行直接调用.调用System.Activator.CreateInstance()编译为IL"call"指令以调用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));
Run Code Online (Sandbox Code Playgroud)

产生这个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
Run Code Online (Sandbox Code Playgroud)


Bra*_*non 5

您无法向类型添加静态方法.您只能将(伪)实例方法添加到类型的实例中.

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

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

  • 这种答案虽然在技术上是正确的,但并没有提供太多有用的东西。这就像问“为什么汽车有 4 个轮子?” 有人回答说“他们有 4 个,因为否则如果他们有 3 个,例如,他们将是三轮车”,这在技术上几乎是不正确的,因为这只是关于这个问题的琐事,但跳过了问题的真正实质,即为什么不是这是否以任何其他方式实现可以实现这一点,并且如果这在其他方式中还不可能实现的话。 (3认同)

Rob*_* S. 5

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


Ama*_*l K 5

虽然 的方法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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Console.Out.WriteBlueLine();
Run Code Online (Sandbox Code Playgroud)

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

Console.Error.WriteBlueLine();
Run Code Online (Sandbox Code Playgroud)

如果您定义了一个扩展方法WriteTable()(用于将表写入控制台),这可能非常有用,因为您还可以将它用于错误流或TextWriter.

较新版本的 C# 允许使用一条using staticfor 语句来更短地Console获取Console.前缀的红色:

using static System.Console;

Out.WriteBlueLine("A blue line");
Error.WriteBlueLine("A blue line");
Run Code Online (Sandbox Code Playgroud)