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