关于C#:我可以向现有的静态类添加扩展方法吗?

Can I add extension methods to an existing static class?

我很喜欢C中的扩展方法,但没有成功地将扩展方法添加到静态类(如控制台)中。

例如,如果我想向控制台添加一个名为"writeblueline"的扩展,那么我可以:

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

我尝试添加一个本地的公共静态方法,并将控制台作为"this"参数…但没有骰子!

1
2
3
4
5
6
7
8
public static class Helpers {
    public static void WriteBlueLine(this Console c, string text)
    {
        Console.ForegroundColor = ConsoleColor.Blue;
        Console.WriteLine(text);
        Console.ResetColor();
    }
}

这没有将"WriteBlueLine"方法添加到控制台…我做错了吗?或者要求不可能的事?


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 public static class ConfigurationManagerWrapper
 {
      public static ConfigurationSection GetSection( string name )
      {
         return ConfigurationManager.GetSection( name );
      }

      .....

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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访问任何内容)。

下面是您将如何使用它:

1
2
3
4
5
6
7
    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(),而不做所有表达式垃圾呢?今天是你的幸运日,因为你得到了2分。正如任何高级.NET开发人员所知道的,new t()很慢,因为它生成了对System.Activator的调用,System.Activator使用反射在调用它之前获取默认的构造函数。该死的微软!但是,我的代码直接调用对象的默认构造函数。

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


这是不可能的。

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

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

下面是一个很好的例子:试图扩展静态MS单元测试类assert:I想要更多的assert方法AreEqual(x1,x2)

唯一的方法是指向不同的类,或者编写一个大约100秒的不同断言方法的包装器。为什么??

如果决定允许实例的扩展,我认为没有逻辑理由不允许静态扩展。一旦实例可以扩展,关于分区库的参数就不起作用。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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);
        }
    }
}


我偶然发现了这条线,同时试图找到一个答案的同一个问题的行动。我没有找到我想要的答案,但我最终还是做了这个。

1
2
3
4
5
6
7
8
public static class MyConsole
{
    public static void WriteLine(this ConsoleColor Color, string Text)
    {
        Console.ForegroundColor = Color;
        Console.WriteLine(Text);  
    }
}

我是这样使用的:

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

不。扩展方法定义需要所扩展类型的实例。很不幸,我不知道为什么需要…


从C 7起,这是不受支持的。然而,在C 8中有关于整合类似内容的讨论,以及值得支持的建议。


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

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

如果编译时已知类型,则new()将编译为il"newobj"指令。newobj接受用于直接调用的构造函数。调用System.Activator.CreateInstance()编译到IL"Call"指令以调用System.Activator.CreateInstance()。当对泛型类型使用new()时,将导致对System.Activator.CreateInstance()的调用。在这一点上,令人讨厌的先生的帖子不清楚…好吧,令人讨厌。

此代码:

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

生成此IL:

1
2
3
4
5
6
7
8
9
  .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

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

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

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


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


写一个扩展方法是不可能的,但是模仿你所要求的行为是可能的。

1
2
3
4
5
6
7
8
9
10
11
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(footerxt)。如果其他类希望访问控制台的其他静态函数,则必须通过其命名空间显式引用它们。

如果希望将所有方法都放在一个位置,则可以始终将它们添加到替换类中。

所以你想要一些像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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的回答中填写了...

No. Extension methods require an instance of an object. You can
however, write a static wrapper around the ConfigurationManager
interface. If you implement the wrapper, you don't need an extension
method since you can just add the method directly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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);
    }
}

是的,在有限的意义上。

1
2
3
4
public class DataSet : System.Data.DataSet
{
    public static void SpecialMethod() { }
}

这是可行的,但控制台却不行,因为它是静态的。

1
2
3
4
5
6
7
8
9
10
11
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拥有的每个方法编写一个代理静态方法。这不一定是件坏事,因为你可以添加如下内容:

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

1
2
3
4
5
 public static void WriteLine(String x)
    {
        System.Console.ForegroundColor = ConsoleColor.Blue;
        System.Console.WriteLine(x);
    }

它的工作方式是将一些东西挂接到标准的writeline中。它可能是一个行数或坏词过滤器或其他。只要在命名空间中指定console,例如webproject1并导入命名空间系统,webproject1.console将在system.console上选择,作为命名空间webproject1中这些类的默认值。因此,如果您从未指定System.console.writeline,则此代码会将所有console.writeline调用转换为蓝色。


您可以使用空值的强制转换使其生效。

1
2
3
4
5
6
7
public static class YoutTypeExtensionExample
{
    public static void Example()
    {
        ((YourType)null).ExtensionMethod();
    }
}

扩展:

1
2
3
4
public static class YourTypeExtension
{
    public static void ExtensionMethod(this YourType x) { }
}

YourType:

1
public class YourType { }

如果您愿意"frig"一点,就可以这样做,方法是生成静态类的一个变量并将其赋给空。但是,该方法对类上的静态调用不可用,因此不确定它的用途有多大:

1
2
3
4
5
6
7
8
9
10
11
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();
    }
}