.net 扩展方法在C#中重载,是否有效?

.net 扩展方法在C#中重载,是否有效?,.net,.net-3.5,c#-3.0,extension-methods,.net,.net 3.5,C# 3.0,Extension Methods,有一个类有一个方法,如下所示: class Window { public void Display(Button button) { // ... } } class WindowExtensions { public void Display(this Window window, object o) { Button button = BlahBlah(o); window.Display(button);

有一个类有一个方法,如下所示:

class Window {
    public void Display(Button button) {
        // ...
    }
}
class WindowExtensions {
    public void Display(this Window window, object o) {
        Button button = BlahBlah(o);
        window.Display(button);
    }
}
是否可以使用另一个更广泛的方法重载该方法,如:

class Window {
    public void Display(Button button) {
        // ...
    }
}
class WindowExtensions {
    public void Display(this Window window, object o) {
        Button button = BlahBlah(o);
        window.Display(button);
    }
}

当我尝试时发生的是,我有无限递归。有什么办法可以让它发挥作用吗?我希望仅当无法调用其他方法时才调用扩展方法。

这是不可能的(另请参见)-可能缺少DLR和
方法

让我们转到规范。首先,我们必须了解方法调用的规则。大致上,您从试图调用方法的实例所指示的类型开始。您在继承链上寻找一个可访问的方法。然后执行类型推断和重载解析规则,如果成功,则调用该方法。只有在找不到这样的方法时,才尝试将该方法作为扩展方法处理。因此,从§7.5.5.2(扩展方法调用)中可以特别看到粗体声明:

其中一种形式的方法调用(§7.5.5.1)

expr.identifier()

expr.identifier(args)

expr.identifier()

expr.identifier(args)

如果调用的正常处理没有找到适用的方法,则会尝试将构造作为扩展方法调用进行处理

除此之外的规则变得有点复杂,但对于您向我们介绍的简单案例,它非常简单。如果没有适用的实例方法,则将调用扩展方法
WindowExtensions.Display(Window,object)
。如果
Window.Display
的参数是按钮或隐式可转换为按钮,则实例方法适用。否则,将调用扩展方法(因为从
对象
派生的所有内容都可以隐式地强制转换为
对象

所以,除非你遗漏了一个重要的部分,否则你所做的一切都会成功

,请考虑下面的例子:

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

在控制台上。

这应该可以工作,编译器几乎总是选择具有可接受签名的实例方法,而不是具有精确签名的扩展方法

据此:

具有可接受属性的实例方法 使用加宽转换的签名 几乎总是被优先于 一种精确的推广方法 签名匹配。如果这导致 在以下情况下绑定到实例方法: 你真的想使用扩展吗 方法,则可以显式调用 使用共享 方法调用约定。这是 还有消除二者歧义的方法 方法,但两者都不更具体

您确定要显式传递按钮吗


或者是
无效显示(按钮)
递归调用自身?

好吧,我认为这有点棘手。如果将
按钮
作为方法参数传递:

Button button = BlahBlah(o);
window.Display(button);
然后是合适的类方法,它总是优先于扩展方法

但若您传递的对象不是
按钮
,那个么就并没有合适的类方法,扩展方法将被调用

var o = new object();
window.Display(o);
所以,从我所看到的,您的示例应该正常工作,扩展方法将在
窗口
实例上调用
Display
方法。无限循环可能是由其他代码引起的


在您的示例中,包含
Display
方法的
Window
类和作为扩展方法参数的
Window
类是否可能实际上是两个不同的类?

有可能,尽管必须小心重载上的参数,但通常最好避免
对象
类型,因为这通常会导致代码混乱。你可能会对C#选择重载的有趣方式感到不满。它将选择一个具有可隐式转换为的类型的“更接近”匹配,而不是具有精确匹配的“更进一步”匹配(请参阅)

您希望您的代码相当清晰,尤其是在一年左右的时间内返回此代码时,会调用什么重载

扩展方法提供了一种非常优雅的方式来扩展对象的功能。您可以添加它最初没有的行为。但是,您必须小心使用它们,因为它们很容易创建混乱的代码。最好的做法是避免使用已经使用的方法名,即使它们是明显的重载,因为它们不会在intellisense类之后显示

这里的问题似乎是扩展方法可以隐式地将按钮转换为对象,因此选择自身作为最佳匹配,而不是实际的显示方法。您可以显式地将扩展方法作为常规静态调用调用,但不能强制它调用底层类的方法

我将更改扩展方法的名称:

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

或者,如果扩展方法上的第二个参数是无法从
按钮
隐式转换为的类型(比如
int
string
),也不会发生这种混淆。

另外,您得到的是一个非常糟糕的体系结构;)SCNRExcept
window.Display(新建按钮())
将实际调用扩展方法,这就是他获得无限递归的原因。@Keith:不,不应该
window.Display(new Button())//窗口就是窗口
应该调用
window.Display(Button)
@Keith:Hm。。实际上
window.Display(new Button())
对我来说很好-它只是调用实例方法。这里的技巧是他从扩展方法中调用
window.Display()
来获得递归。@Jason:我想他得到递归是因为通常
window.Display(new Button())命中实例方法,但在扩展方法内部它选择自己