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

发布于 2024-08-19 08:14:12 字数 406 浏览 7 评论 0原文

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

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

是否可以用另一个更广泛的方法重载该方法,如下所示:

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

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

Having a class that has a method, like this:

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

is it possible to overload the method with 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

挽你眉间 2024-08-26 08:14:12

让我们看看规格。首先,我们要了解方法调用的规则。粗略地说,您从尝试调用方法的实例指示的类型开始。您沿着继承链向上寻找可访问的方法。然后,您执行类型推断和重载解析规则,并在成功时调用该方法。仅当未找到此类方法时,您才会尝试将该方法作为扩展方法进行处理。因此,从 §7.5.5.2(扩展方法调用)中可以看到,特别是粗体语句:

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

expr.identifier()

expr.identifier(args)

expr.identifier()

expr.identifier(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

在控制台上。

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.

挽心 2024-08-26 08:14:12

这是可能的,尽管您必须小心重载的参数 - 通常最好避免 object 类型,因为这通常会导致代码混乱。您可能会犯下 C# 选择重载的有趣方式。它将选择一种“更接近”的匹配类型,该类型可以隐式转换为具有完全匹配的“进一步”匹配类型(请参阅此问题)。

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< /code> 或 string)这种混乱也不会发生。

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.

八巷 2024-08-26 08:14:12

这应该可行,编译器几乎总是会选择具有可接受签名的实例方法,而不是具有确切签名的扩展方法。

根据这篇文章

具有可接受的实例方法
使用加宽转换的签名
几乎总是会优先于
具有精确的扩展方法
签名匹配。如果这导致
绑定到实例方法时
您确实想使用该扩展程序
方法,您可以显式调用
使用共享的扩展方法
方法调用约定。这是
也是消除两个歧义的方法
方法,当两者都不更具体时。

您确定要明确传递一个按钮吗?

或者是 void Display(Button button) 递归调用自身?

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?

迷离° 2024-08-26 08:14:12

嗯,我相信这有点棘手。如果您将 Button 作为方法参数传递:

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

那么就有合适的类方法,该方法始终优先于扩展方法。

但是,如果您传递的对象不是 Button,则没有合适的类方法,并且将调用扩展方法。

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

因此,从我看来,您的示例应该可以正常工作,并且扩展方法将调用 Window 实例上的 Display 方法。无限循环可能是由其他一些代码引起的。

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

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?

如若梦似彩虹 2024-08-26 08:14:12

这是不可能的(另请参阅 Monkeypatching For Humans) - 也许使用 DLR 和 method_missing

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文