14

为什么在具有接口类型约束的泛型方法中显式 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))

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

4

2 回答 2

7

这里的关键是要记住这一点,IBase.Method并且IDerived.Method是两种完全不同的方法。我们只是碰巧给了他们相似的名字和签名。因为任何实现的东西IDerived也会实现IBase,这意味着它将有两个名为Method不带参数的方法。一个属于IDerived,一个属于IBase

编译器在编译的时候GenericMethod只知道泛型参数至少会实现IBase,所以只能保证IBase.Method实现存在。这就是所谓的方法。

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

在您的情况下,编译器会发出 IL GenericMethod,看起来像这样:

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 ,都会调用 base 。

于 2016-07-22T22:20:24.457 回答
1

添加到凯尔的回答中,我不能在评论中这样做,因为我还没有足够的声誉......

我认为这说明了:

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

移除对象并让参数为 T,因此无需进行强制转换,仍然调用 IBase.Method。

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

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

于 2016-07-25T15:04:21.467 回答