C#中的扩展方法重载,它有效吗?

时间:2010-01-22 14:58:26

标签: .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);
    }
}

我尝试时发生的事情是我有无限的递归。有没有办法让这项工作?我希望只有在无法调用其他方法时才调用扩展方法。

5 个答案:

答案 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(例如intstring隐式转换)的类型,则此混淆也不会发生

答案 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类是否实际上是两个不同的类?