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

Why do explicit interface calls on generics always call the base implementation?

为什么在具有接口类型约束的泛型方法中显式 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 调用中计算结果为真:

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

所以答案不是 T 总是被视为 IBase 由于通用类型约束 "where T : class, IBase"。

这里的关键是要记住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 之间没有 virtual/override 关系,因此无论在运行时用什么类型替代 T,都会调用基类。

添加到 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++ 模板。