拥有一个具有方法的类,如下所示:
class Window {
public void Display(Button button) {
// ...
}
}
是否可以使用更宽泛的方法重载该方法,如下所示:
class WindowExtensions {
public void Display(this Window window, object o) {
Button button = BlahBlah(o);
window.Display(button);
}
}
我尝试时发生的事情是我有无限的递归。有没有办法让这项工作?我希望只有在无法调用其他方法时才调用扩展方法。
答案 0 :(得分:9)
我们来看看规范。首先,我们必须了解方法调用的规则。粗略地说,您从您尝试调用方法的实例指示的类型开始。您继续沿着继承链寻找可访问的方法。然后,您执行类型推断和重载解析规则,并在成功时调用该方法。只有在找不到这样的方法时,才会尝试将该方法作为扩展方法处理。因此,从§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
在控制台上。
答案 1 :(得分:2)
虽然你必须小心过载的参数,但是有可能避免使用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
隐式转换)的类型,则此混淆也不会发生
答案 2 :(得分:1)
这应该有效,编译器几乎总是会选择具有可接受签名的实例方法,而不是具有确切签名的扩展方法。
根据article:
具有可接受的实例方法 签名使用扩展转换 几乎总是优先考虑 一个精确的扩展方法 签名匹配。如果这导致 绑定到实例方法时 你真的想使用扩展名 方法,你可以显式调用 使用共享的扩展方法 方法调用约定。这是 也是消除两个歧义的方法 两种方法都不具体。
您确定要明确传递按钮吗?
或void Display(Button button)
递归调用自己?
答案 3 :(得分:0)
这是不可能的(也见Monkeypatching For Humans) - 也许是DLR和method_missing
。
答案 4 :(得分:0)
嗯,我相信这有点棘手。如果您将Button
作为方法参数传递:
Button button = BlahBlah(o);
window.Display(button);
然后有合适的类方法,它始终优先于扩展方法。
但是如果传递的对象不是Button
,那么就没有合适的类方法,并且会调用扩展方法。
var o = new object();
window.Display(o);
因此,从我看到的,您的示例应该正常工作,扩展方法将在Display
实例上调用Window
方法。无限循环可能是由其他一些代码引起的。
在您的示例中,Window
类包含Display
类,并且作为扩展方法参数的Window
类是否实际上是两个不同的类?