在 C# 中重载扩展方法,它有效吗?
Posted
技术标签:
【中文标题】在 C# 中重载扩展方法,它有效吗?【英文标题】:Extension methods overloading in C#, does it work? 【发布时间】:2011-01-08 06:02:14 【问题描述】:拥有一个有方法的类,像这样:
class Window
public void Display(Button button)
// ...
是否可以用另一个更广泛的方法重载该方法,如下所示:
class WindowExtensions
public void Display(this Window window, object o)
Button button = BlahBlah(o);
window.Display(button);
当我尝试时发生的事情是我有无限递归。有没有办法让它工作?我希望仅在无法调用其他方法时才调用扩展方法。
【问题讨论】:
你得到的额外是一个非常糟糕的架构;) SCNR 【参考方案1】:这是不可能的(另请参阅 Monkeypatching For Humans)- 可能使用 DLR 和 method_missing
。
【讨论】:
对不起,这个答案不正确。有可能的。请参阅此线程中的许多其他答案。【参考方案2】:让我们看一下规范。首先,我们必须了解方法调用的规则。粗略地说,您从尝试调用方法的实例所指示的类型开始。您沿着继承链向上寻找可访问的方法。然后,您执行类型推断和重载解析规则,如果成功则调用该方法。只有在没有找到这样的方法时,您才会尝试将该方法作为扩展方法处理。因此,从第 7.5.5.2 节(扩展方法调用)看,尤其是粗体声明:
在一种形式的方法调用(§7.5.5.1)中
expr.identifier()
expr.identifier(args)
expr.identifier<typeargs>()
expr.identifier<typeargs>(args)
如果调用的正常处理找不到适用的方法,则尝试将构造作为扩展方法调用处理。
除此之外的规则有点复杂,但对于您向我们展示的简单案例来说,它非常简单。如果没有适用的实例方法,则将调用扩展方法WindowExtensions.Display(Window, object)
。如果Window.Display
的参数是按钮或隐式 可转换为按钮,则实例方法适用。否则,将调用扩展方法(因为从object
派生的所有内容都可以隐式转换为object
)。
因此,除非您遗漏了重要的部分,否则您尝试做的事情将会奏效。
因此,请考虑以下示例:
class Button
class Window
public void Display(Button button)
Console.WriteLine("Window.Button");
class NotAButtonButCanBeCastedToAButton
public static implicit operator Button(
NotAButtonButCanBeCastedToAButton nab
)
return new Button();
class NotAButtonButMustBeCastedToAButton
public static explicit operator Button(
NotAButtonButMustBeCastedToAButton nab
)
return new Button();
static class WindowExtensions
public static void Display(this Window window, object o)
Console.WriteLine("WindowExtensions.Button: 0", o.ToString());
Button button = BlahBlah(o);
window.Display(button);
public static Button BlahBlah(object o)
return new Button();
class Program
static void Main(string[] args)
Window w = new Window();
object o = new object();
w.Display(o); // extension
int i = 17;
w.Display(i); // extension
string s = "Hello, world!";
w.Display(s); // extension
Button b = new Button();
w.Display(b); // instance
var nab = new NotAButtonButCanBeCastedToAButton();
w.Display(b); // implicit cast so instance
var nabexplict = new NotAButtonButMustBeCastedToAButton();
w.Display(nabexplict); // only explicit cast so extension
w.Display((Button)nabexplict); // explictly casted so instance
这将打印出来
WindowExtensions.Button: System.Object
Window.Button
WindowExtensions.Button: 17
Window.Button
WindowExtensions.Button: Hello, world!
Window.Button
Window.Button
Window.Button
WindowExtensions.Button: NotAButtonButMustBeCastedToAButton
Window.Button
Window.Button
在控制台上。
【讨论】:
w.Display(b); // implicit cast so instance
--- 应该是:--- w.Display( **nab**); // implicit cast so instance
【参考方案3】:
嗯,我相信这有点棘手。如果您将Button
作为方法参数传递:
Button button = BlahBlah(o);
window.Display(button);
然后有合适的类方法总是优先于扩展方法。
但是如果你传递的对象不是Button
,那么就没有合适的类方法并且会调用扩展方法。
var o = new object();
window.Display(o);
因此,据我所知,您的示例应该可以正常工作,并且扩展方法将在 Window
实例上调用 Display
方法。无限循环可能是由其他代码引起的。
在您的示例中包含Display
方法的Window
类和作为扩展方法参数的Window
类是否有可能实际上是两个不同的类?
【讨论】:
除了window.Display(new Button());
实际上会调用扩展方法,这就是他得到无限递归的原因。
@Keith:不,不应该; window.Display(new Button()) // window is Window
应该调用 Window.Display(Button)
。
@Keith: 嗯.. 实际上window.Display(new Button())
对我来说工作得很好——它只是调用实例方法。
这里的诀窍是他从扩展方法中调用window.Display()
来获得递归。
@Jason:我认为他得到了递归,因为通常window.Display(new Button());
会命中实例方法,但在扩展方法中它会选择自己。我在 C# 编译器中看到过与之前类似的问题,但我重新创建了这个问题,所以可能是错误的。【参考方案4】:
这是可能的,尽管您必须小心重载的参数 - 通常最好避免使用 object
类型,因为这通常会导致代码混乱。您可能会犯 C# 选择重载的有趣方式。它将选择一个“更接近”的匹配类型,该类型可以隐式转换为具有完全匹配的“进一步”匹配(参见this question)。
Button myButton = // get button
Window currentWindow = // get window
// which method is called here?
currentWindow.Display( myButton );
您希望您的代码相当清晰,尤其是在一年左右返回此代码时,调用的是什么重载。
扩展方法提供了一种非常优雅的方式来扩展对象的功能。您可以添加它最初没有的行为。但是你必须小心它们,因为它们很容易产生令人困惑的代码。最好避免已使用的方法名称,即使它们是明显的重载,因为它们不会出现在智能感知中的类之后。
这里的问题似乎是扩展方法可以隐式地将您的按钮转换为对象,因此选择自己作为最佳匹配,而不是实际的显示方法。您可以像普通静态调用一样显式调用扩展方法,但不能强制它调用底层类的方法。
我会更改扩展方法的名称:
object somethingToMakeIntoAButton = // get object
Window currentWindow = // get window
// which method is called here?
currentWindow.DisplayButton( somethingToMakeIntoAButton );
那么……
class WindowExtensions
public void DisplayButton(this Window window, object o)
Button button = BlahBlah(o);
// now it's clear to the C# compiler and human readers
// that you want the instance method
window.Display(button);
或者,如果扩展方法的第二个参数是无法从 Button
隐式转换为的类型(例如 int
或 string
),也不会发生这种混淆。
【讨论】:
更改方法名称的好建议。但是,如果您尝试运行 Jason 提供的示例,您会发现extension method
并没有选择自己作为最佳匹配,而是 compiler
从扩展方法中选择要调用的实例方法。
那么它不应该得到递归错误,但无论哪种方式,我认为建议都是:参数类型可以相互派生的重载总是一个坏主意。【参考方案5】:
这应该可行,编译器几乎总是会选择具有可接受签名的实例方法,而不是具有确切签名的扩展方法。
据此article:
具有可接受的实例方法 使用扩大转换的签名 几乎总是优先于 具有精确的扩展方法 签名匹配。如果这导致 绑定到实例方法时 你真的想使用扩展 方法,您可以显式调用 使用共享的扩展方法 方法调用约定。这是 也是消除两个歧义的方法 两者都不是更具体的方法。
你确定你明确地传递了一个按钮吗?
还是void Display(Button button)
递归调用自己?
【讨论】:
+1,但我认为这里的问题是“几乎总是首选”。在扩展方法中,它更喜欢自己,隐式转换从Button
到object
。因此递归。以上是关于在 C# 中重载扩展方法,它有效吗?的主要内容,如果未能解决你的问题,请参考以下文章