我是 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
”方法...我做错了吗?还是要求不可能的事?
Helpers.WriteBlueLine(null, "Hi");
:)
不可以。扩展方法需要一个对象的实例变量(值)。但是,您可以围绕 ConfigurationManager
接口编写一个静态包装器。如果您实现包装器,则不需要扩展方法,因为您可以直接添加方法。
public static class ConfigurationManagerWrapper
{
public static ConfigurationSection GetSection( string name )
{
return ConfigurationManager.GetSection( name );
}
.....
public static ConfigurationSection GetWidgetSection()
{
return GetSection( "widgets" );
}
}
你可以在 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 的调用,该调用在调用之前使用反射来获取默认构造函数。该死的微软!但是我的代码直接调用对象的默认构造函数。
静态扩展会比这更好,但绝望的时候需要绝望的措施。
XConsole
、ConsoleHelper
等方法设置一些命名约定会更好、更容易。
(null as DataSet).Create();
的一种替代方法可能是 default(DataSet).Create();
。
这是不可能的。
是的,我认为 MS 在这里犯了一个错误。
他们的决定没有意义,并迫使程序员(如上所述)编写一个毫无意义的包装类。
这是一个很好的例子:尝试扩展静态 MS 单元测试类 Assert:我还想要 1 个 Assert 方法 AreEqual(x1,x2)
。
做到这一点的唯一方法是指向不同的类或编写一个包含 100 多个不同 Assert 方法的包装器。为什么!?
如果决定允许扩展实例,我认为没有逻辑理由不允许静态扩展。一旦可以扩展实例,关于划分库的论点就站不住脚了。
Assert.Throws
Static
类的净收益为零。一开始它总是似乎是一个好主意,但在实践中有太多理由说明为什么这是一个反模式。根本没有 pointless 包装类,而是有一个非常有意义且专门构建的实用程序或帮助类,可以将所有自定义逻辑保存在一个地方。不要试图复制 Assert
上的所有函数,只对您的自定义函数进行编码,开发人员在需要时调用您的自定义逻辑,其余部分使用 Assert。
我在试图找到 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à");
也许您可以使用自定义命名空间和相同的类名添加一个静态类:
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);
}
}
}
从 C#7 开始,不支持此功能。但是有 discussions about integrating something like that in C#8 和 proposals worth supporting。
没有。扩展方法定义需要您要扩展的类型的实例。不幸的是;我不确定为什么需要它...
您不能将静态方法添加到类型。您只能将(伪)实例方法添加到类型的实例。
this
修饰符的目的是告诉 C# 编译器将 .
左侧的实例作为静态/扩展方法的第一个参数传递。
在向类型添加静态方法的情况下,没有实例可以传递给第一个参数。
至于扩展方法,扩展方法本身是静态的;但它们被调用就好像它们是实例方法一样。由于静态类不可实例化,因此您永远不会有该类的实例来调用扩展方法。由于这个原因,编译器不允许为静态类定义扩展方法。
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
当我学习扩展方法并且没有成功时,我尝试使用 System.Environment 执行此操作。正如其他人提到的那样,原因是因为扩展方法需要类的实例。
不可能编写扩展方法,但是可以模仿您要求的行为。
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 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 调用变为蓝色。
以下被拒绝为 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);
}
}
您可以在 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 { }
不幸的是不,你不能扩展静态类
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
用这个
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# 允许使用 Console
的 using static
语句将 Console.
前缀变为红色,从而使其更短:
using static System.Console;
Out.WriteBlueLine("A blue line");
Error.WriteBlueLine("A blue line");
如果您愿意通过创建静态类的变量并将其分配给 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();
}
}