我可以向现有静态类添加扩展方法吗?
本文关键字:扩展 方法 添加 静态类 可以向 我可以 | 更新日期: 2023-09-27 17:47:46
我是 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
...我做错了吗?还是要求不可能的事情?
No. 扩展方法需要对象的实例变量(值(。 但是,您可以在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; }
}
这是它的工作原理。 虽然在技术上无法编写静态扩展方法,但此代码利用了扩展方法中的漏洞。 这个漏洞是,您可以在空对象上调用扩展方法而不会获得空异常(除非您通过@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((,而不做所有这些表达式垃圾?好吧,今天是你的幸运日,因为你得到了一个2fer。 正如任何高级 .NET 开发人员所知,新的 T(( 很慢,因为它生成了对 System.Activator 的调用,该调用在调用之前使用反射来获取默认构造函数。 该死的Microsoft!但是,我的代码直接调用对象的默认构造函数。
静态扩展会比这更好,但绝望的时刻需要绝望的措施。
是不可能的。
是的,我认为MS在这里犯了一个错误。
他们的决定没有意义,并迫使程序员编写(如上所述(一个毫无意义的包装类。
这是一个很好的例子:尝试扩展静态 MS 单元测试类断言:我想要 1 个 断言方法 AreEqual(x1,x2)
.
执行此操作的唯一方法是指向不同的类或围绕 100 多个不同的 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à");
从 C#7 开始,这不受支持。然而,有关于在 C#8 中集成类似内容的讨论和值得支持的提案。
也许您可以添加一个具有自定义命名空间和相同类名的静态类:
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);
}
}
}
不。 扩展方法定义需要要扩展的类型的实例。 这很不幸;我不确定为什么需要它...
不能向类型添加静态方法。 只能将(伪(实例方法添加到类型的实例。
this
修饰符的要点是告诉 C# 编译器将.
左侧的实例作为静态/扩展方法的第一个参数传递。
在向类型添加静态方法的情况下,第一个参数没有要传递的实例。
至于扩展方法,扩展方法本身是静态的;但它们被调用,就好像它们是实例方法一样。 由于静态类不可实例化,因此永远不会有从中调用扩展方法的类实例。 因此,编译器不允许为静态类定义扩展方法。
Obnoxious先生写道:"正如任何高级.NET开发人员都知道的那样,新的T((很慢,因为它生成了对System.Activator的调用,该调用在调用之前使用反射来获取默认构造函数。
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
当我学习扩展方法时,我试图用System.Environment来做到这一点,但没有成功。正如其他人提到的,原因是扩展方法需要类的实例。
尽管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");
编写扩展方法,但可以模仿您要求的行为。
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(。如果其他类想要访问控制台的其他静态函数,则必须通过其命名空间显式引用它们。
如果要将所有方法都放在一个位置,则始终可以将所有方法添加到替换类中。
所以你会有类似的东西
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的回答中填写了...
。
不。扩展方法需要对象的实例。您可以 但是,在配置管理器周围编写静态包装器 接口。如果实现包装器,则不需要扩展 方法,因为您可以直接添加方法。
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);
}
}
是的,在有限的意义上。
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(时,WebProject1.Console 将优先于 System.Console 作为命名空间 WebProject1 中这些类的默认选项。因此,此代码会将所有 Console.WriteLine 调用转换为蓝色,只要您从未指定 System.Console.WriteLine。
您可以在 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 MyConsole
{
public static void WriteBlueLine(string text)
{
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine(text);
Console.ResetColor();
}
}
//...
MyConsole.WriteBlueLine("I'm so blue...");
Console.WriteLine("...and I'm not.");
这是最少的额外打字工作,作为奖励,它使事情保持透明......
毕竟,即使是常规的扩展方法也只是帮助程序方法的简写。它不允许你对类(实例(做任何你无法从常规方法做的事情。
愿意通过创建静态类的变量并将其分配给 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();
}
}