为什么在具有接口类型约束的泛型方法中的显式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。
答案 0 :(得分:7)
这里的关键是要记住IBase.Method
和IDerived.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 ++模板不同。