为什么对泛型的显式接口调用总是调用基本实现?

时间:2016-07-22 21:45:30

标签: c# .net generics explicit-interface

为什么在具有接口类型约束的泛型方法中的显式C#接口调用总是调用基本实现?

例如,请考虑以下代码:

public interface IBase
{
    string Method();
}

public interface IDerived : IBase
{
    new string Method();
}

public class Foo : IDerived
{
    string IBase.Method()
    {
        return "IBase.Method";
    }

    string IDerived.Method()
    {
        return "IDerived.Method";
    }
}

static class Program
{
    static void Main()
    {
        IDerived foo = new Foo();
        Console.WriteLine(foo.Method());
        Console.WriteLine(GenericMethod<IDerived>(foo));
    }

    private static string GenericMethod<T>(object foo) where T : class, IBase
    {
        return (foo as T).Method();
    }
}

此代码输出以下内容:

  

IDerived.Method
  IBase.Method

而不是人们的期望:

  

IDerived.Method
  IDerived.Method

似乎没有办法(缺少反射)来调用在运行时决定的类型的隐藏的,更加派生的显式接口实现。

编辑:要明确的是,以下if检查在上面的GenericMethod调用中评估为true:

  

if(typeof(T)== typeof(IDerived))

所以答案并不是因为泛型类型约束“T:class,IBase”,T总是被视为IBase。

2 个答案:

答案 0 :(得分:7)

这里的关键是要记住IBase.MethodIDerived.Method是两种完全不同的方法。我们碰巧给了他们相似的名字和签名。由于实现IDerived的任何内容都实现了IBase,这意味着它将有两个名为Method的方法不带参数。一个属于IDerived,一个属于IBase

所有编译器都知道编译GenericMethod时通用参数至少会实现IBase,因此它只能保证IBase.Method实现存在。这就是所谓的方法。

与C ++模板不同,每当编译方法时,通用替换都不会发生(对于每个使用的模板参数组合,模板都会发生一次)。相反,该方法只需编译一次,以便在运行时可以替换任何类型。

在您的情况下,编译器为GenericMethod发出IL,如下所示:

IL_0000:  ldarg.0     
IL_0001:  isinst      <T> 
IL_0006:  unbox.any   <T>
IL_000B:  box         <T>    
IL_0010:  callvirt    IBase.Method
IL_0015:  ret         

请注意,它明确调用IBase.Method。该方法与IDerived.Method之间没有虚拟/覆盖关系,因此无论在运行时中用什么类型替换T,都会调用所有基础。

答案 1 :(得分:1)

添加到Kyle的答案,我在评论中无法做到,因为我还没有足够的声誉......

我认为这是在说明:

private static string GenericMethod<T>(T foo) where T : class, IBase
{
    return foo.Method() + " "  + typeof(T) + " " + typeof(Foo);
}

删除对象并使参数为T,因此不需要as-cast仍然会调用IBase.Method。

我很确定这完全是由于C#规范中的 4.4.4满足约束所致。

在这方面,C#泛型的行为与C ++模板不同。