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

我是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…我做错了吗?或者要求不可能的事情?

243704 次浏览

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

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

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

我试着用系统来做这件事。当时我正在学习扩展方法,但没有成功。正如其他人提到的,原因是扩展方法需要类的实例。

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

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

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


.....


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

这是不可能的。

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

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

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

做到这一点的唯一方法是指向不同的类或围绕100个不同的Assert方法编写包装器。# EYZ0

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

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

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

如果你愿意通过创建静态类的变量并将其赋值为null来“frig”它,你可以这样做。然而,这个方法对类的静态调用是不可用的,所以不确定它会有多少用处:

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

你能在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()而不做所有的表达式垃圾? 今天是你的幸运日,因为你有一个2。任何高级的。net开发人员都知道,new T()很慢,因为它生成了对System的调用。激活器,它在调用默认构造函数之前使用反射获取默认构造函数。该死的微软! 但是,我的代码直接调用对象的默认构造函数

静态扩展将比这更好,但紧急时刻需要紧急措施。

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

obannoy先生写道:“任何高级的。net开发人员都知道,new T()很慢,因为它生成了一个对System的调用。激活器,在调用它之前使用反射来获取默认构造函数”。

如果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

是的,在有限的意义上。

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的每个方法编写一个代理静态方法。控制台。这并不一定是一件坏事,因为你可以添加这样的内容:

    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。控制台将被选择在系统之上。控制台作为命名空间WebProject1中的这些类的默认值。这段代码将会打开所有的控制台。如果你没有指定System.Console.WriteLine,则WriteLine调用为蓝色。

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

< p >。扩展方法需要对象的实例。你可以 但是,在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);
}
}

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

我在试图找到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 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.
}

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

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

使用这个

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


.....


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

虽然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#允许使用using static语句使Console得到Console.前缀的红色:

using static System.Console;


Out.WriteBlueLine("A blue line");
Error.WriteBlueLine("A blue line");

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

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