开发者

Extension methods overloading in C#, does it work?

Having a class that has a method, like this:

class Window {
    public void Display(Button button) {
        // ...
    }
}

is it possible to overload the method wi开发者_如何学JAVAth another one that is more broad, like this:

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

What happened when I tried is that I have infinite recursion. Is there a way to make that work? I want the extension method to be called only when the other method can't be called.


Let's go to the specification. First, we have to understand the rules for method invocations. Roughly, you start with the type indicated by the instance you are trying to invoke a method on. You walk up the inheritance chain looking for an accessible method. Then you do your type inference and overload resolution rules and invoke the method if that succeeds. Only if no such method is found do you try to process the method as an extension method. So from §7.5.5.2 (Extension method invocations) see, in particular, the bolded statement:

In a method invocation (§7.5.5.1) of one of the forms

expr.identifier()

expr.identifier(args)

expr.identifier<typeargs>()

expr.identifier<typeargs>(args)

if the normal processing of the invocation finds no applicable methods, an attempt is made to process the construct as an extension method invocation.

The rules beyond that get a little complicated, but for the simple case you've presented to us it's quite simple. If there is no applicable instance method then the extension method WindowExtensions.Display(Window, object) will be invoked. The instance method is applicable if the parameter to Window.Display is a button or is implicitly castable to a button. Otherwise, the extension method will be invoked (because everything that derives from object is implicitly castable to an object).

So, unless there is an important bit that you are leaving out, what you are trying to do will work.

So, consider the following example:

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

This will print

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

on the console.


It is possible, although you have to be careful with the parameters on overloads - it's usually a good idea to avoid object types as that often causes confusing code. You can fall foul of the funny way C# picks overloads. It will choose a 'closer' match with types that can be implicitly cast to over a 'further' one that has exact matches (see this question).

Button myButton = // get button
Window currentWindow = // get window

// which method is called here?
currentWindow.Display( myButton );

You want your code to be fairly clear, especially when returning to this code in a year or so, what overload is being called.

Extension methods provide a really elegant way to expand the functionality of objects. You can add behaviour that it didn't have originally. You have to be careful with them though, as they're very prone to creating confusing code. It's best practice to avoid method names already used, even if they are clear overloads, as they don't show up after the class in intellisense.

Here the problem appears to be that the extension method can implicitly convert your button to an object, and so picks itself as the best match, instead of the actual display method. You can explicitly call the extension method as a normal static call, but you can't force it to call the underlying class's method.

I would change the name of the extension method:

object somethingToMakeIntoAButton = // get object
Window currentWindow = // get window

// which method is called here?
currentWindow.DisplayButton( somethingToMakeIntoAButton );

Then...

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

Alternatively if the second parameter on the extension method was a type that couldn't be implicitly converted to from Button (say int or string) this confusion wouldn't happen either.


That should work, the compiler will almost always pick an instance method with an acceptable signature over an extension method with the exact signature.

According to this article:

An instance method with an acceptable signature using widening conversion will almost always be preferred over an extension method with an exact signature match. If this results in binding to the instance method when you really want to use the extension method, you can explicitly call the extension method using the shared method calling convention. This is also the way to disambiguate two methods when neither is more specific.

Are you sure you're explicitly passing a Button?

Or is void Display(Button button) recursively calling itself?


Well, I believe this is a little tricky. If you pass Button as a method parameter:

Button button = BlahBlah(o);
window.Display(button);

then there is suitable class method which always takes precedence over the extension method.

But if you pass object that is not Button then there is no suitable class method and extension method will be invoked.

var o = new object();
window.Display(o);

So, from what I see, your example should work correctly and extension method will call Display method on the Window instance. The infinite loop might be caused by some other code.

Is there any chance that Window class containing Display method in your example and Window class that is a parameter to the extension method are actually two different classes?


It's not possible (also see Monkeypatching For Humans) - maybe with DLR and method_missing.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜