扩展方法与父类方法行为

时间:2010-02-04 11:33:34

标签: c# extension-methods

请查看以下代码:

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方法时究竟发生了什么?

3 个答案:

答案 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)