为什么对泛型的显式接口调用总是调用基础实现?
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.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
之间没有 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++ 模板。
为什么在具有接口类型约束的泛型方法中显式 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.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
之间没有 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++ 模板。