请查看以下代码:
class A
{
public string DoSomething(string str)
{
return "A.DoSomething: " + str;
}
}
class B : A
{
}
static class BExtensions
{
public static string DoSomething(this B b, string str)
{
return "BExtensions.DoSomething: " + str;
}
}
class Program
{
static void Main(string[] args)
{
var a = new A();
var b = new B();
Console.WriteLine(a.DoSomething("test"));
Console.WriteLine(b.DoSomething("test"));
Console.ReadKey();
}
}
代码的输出是:
A.DoSomething:test
A.DoSomething:test
编译时不会发出警告。
我的问题是:为什么在代码编译时没有警告,以及在调用DoSomething
方法时究竟发生了什么?
答案 0 :(得分:6)
调用方法时发生的事情很简单:只是实例方法调用。由于C#是早期绑定的,因此所有方法都在编译时解析。此外,实例方法比扩展方法更受欢迎,因此这就是永远不会调用扩展方法的原因。
请参阅this:
您可以使用扩展方法来扩展类或接口,但不能覆盖它们。永远不会调用与接口或类方法具有相同名称和签名的扩展方法。在编译时,扩展方法的优先级始终低于类型本身中定义的实例方法。
换句话说,如果类型具有名为
Process(int i)
的方法,并且您具有具有相同签名的扩展方法,则编译器将始终绑定到实例方法。当编译器遇到方法调用时,它首先在类型的实例方法中查找匹配项。如果未找到匹配项,它将搜索为该类型定义的任何扩展方法,并绑定到它找到的第一个扩展方法。
答案 1 :(得分:4)
基本上,编译器将始终使用实例方法(如果可用),只有在其他所有方法都失败时才使用扩展方法。来自C#3.0规范的第7.5.5.2节:
在方法调用(第7.5.5.1节)中 其中一种形式
- expr。 identifier()
- expr。标识符(args)
- expr。标识符< typeargs> ()
- expr。标识符< typeargs> (args)
如果正常处理了 调用找不到适用的 方法,尝试处理 构造作为扩展方法 调用
这是我发现扩展方法的方法之一......扩展方法DoSomething
将永远不会作为扩展方法调用(虽然它可以使用普通的静态方法调用语法)...然而编译器甚至没有发出警告:(
答案 2 :(得分:1)
我可能不完全正确,但编译器做了这样的事情: 当它到达
b.DoSomething("test")
它试图在这个或基类中找到具有相同签名的方法,当它发现基类中的方法映射对它的调用时,只需忽略扩展方法。
但是,如果您在同一行中从B声明中删除基类A,编译器将检查此类或基类中是否存在具有此类签名的方法,并将通过调用静态方法BExtensions.DoSomething来替换它。
您可以使用.NET Reflector检查它。
当B来自A:
.locals init (
[0] class Test.A a,
[1] class Test.B b)
...
ldloc.1 // loading local variable b
ldstr "test"
callvirt instance string Test.A::DoSomething(string)
call void [mscorlib]System.Console::WriteLine(string)
当B派生自System.Object时:
.locals init (
[0] class Test.A a,
[1] class Test.B b)
...
ldloc.1 // loading local variable b
ldstr "test"
call string Test.BExtensions::DoSomething(class Test.B, string)
call void [mscorlib]System.Console::WriteLine(string)