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

时间:2008-10-30 03:54:04

标签: c# static extension-methods

我是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'方法......我做错了吗?或者要求不可能?

15 个答案:

答案 0 :(得分:256)

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

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

      .....

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

答案 1 :(得分:85)

您可以在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(...)

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

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

答案 2 :(得分:43)

不可能。

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

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

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

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

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

答案 3 :(得分: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);
        }
    }
}

答案 4 :(得分:16)

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

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

我这样使用它:

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

答案 5 :(得分:10)

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

答案 6 :(得分:9)

答案 7 :(得分: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));

产生这个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

答案 8 :(得分:5)

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

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

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

答案 9 :(得分:4)

当我学习扩展方法并且没有成功时,我尝试用System.Environment做这个。原因是,正如其他人所提到的,因为扩展方法需要类的实例。

答案 10 :(得分:1)

是的,在有限的意义上。

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时,将通过System.Console选择WebProject1.Console作为命名空间WebProject1中的那些类的默认值。因此,只要您从未指定过System.Console.WriteLine,此代码就会将所有Console.WriteLine调用变为蓝色。

答案 11 :(得分:1)

以下被拒绝作为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);
    }
}

答案 12 :(得分:1)

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

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

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

* Note控制台必须通过您放置在其中的名称空间添加。

答案 13 :(得分:0)

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

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

扩展名:

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

YourType:

public class YourType { }

答案 14 :(得分:-4)

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