我在互联网和stackoverflow上做了一些基本的搜索,当涉及泛型版本方法和非泛型版本方法时,我看到了很多关于重载解析的讨论。我知道重载解析是在编译时完成的 - 因此如果我有这段代码:
public class A<T>
{
public void DoStuff(T value)
{
InternalDoStuff(value);
}
protected void InternalDoStuff(int value)
{
Console.WriteLine("Non-generic version");
}
protected void InternalDoStuff(T value)
{
Console.WriteLine("Generic version");
}
}
public class Test
{
static void Main (string [] args)
{
A<int> a = new A<int> ();
a.DoStuff(100);
}
}
输出将是“通用版本”,因为“InternalDoStuff”的分辨率已由编译器整理出来,编译器看到的是“在DoStuff中使用T类型参数调用InternalDoStuff”。
但是我不知道这是否会有所不同:
public class B : A <int>
{
}
public class Test
{
static void Main (string [] args)
{
B b = new B ();
b.DoStuff(100);
}
}
现在我可以说编译器有足够的信息来决定“B是A的特定版本”,因此调用InternalDoStuff的非泛型版本吗?
分析这种过载分辨率是否有任何一般原则?
答案 0 :(得分:4)
第二种方法在任何意义上都与第一种方法没有区别。
从A中派生B类绝不会改变为A类生成的IL代码.B只是继承这些方法。
如果查看A类的IL代码,可以看到它编译为调用泛型版本而不是非泛型版本 -
.method public hidebysig instance void DoStuff(!T 'value') cil managed
{
.maxstack 8
L_0000: nop
L_0001: ldarg.0
L_0002: ldarg.1
L_0003: call instance void
ConsoleApplication1.A`1<!T>::InternalDoStuff(!0) <-- Generic version
L_0008: nop
L_0009: ret
}
来自Jon Skeet的文章here -
提醒一下,当你有两个时,会发生重载 具有相同名称但签名不同的方法。在编译时, 根据编译器,编译器确定它将调用哪一个 编译参数的时间类型和方法调用的目标。 (我假设你没有在这里使用动态,这使事情复杂化 有些。)
他提到使用动态延迟解析直到运行时。这段代码将为您的两种方法调用非泛型版本方法 -
public void DoStuff(T value)
{
dynamic dynamicValue = value;
InternalDoStuff(dynamicValue);
}
请参阅Jon Skeet和Eric Lippert详细描述的答案。
答案 1 :(得分:2)
在C ++中,程序在执行期间可能创建的每种类型都必须在编译时生成。虽然C ++模板看起来像C#泛型,但它们的行为更类似于替换宏。因为编译器单独生成可能由泛型类型替换产生的每个类,所以它可以分别为每个类评估重载决策等内容。 C#泛型不能那样工作。
C#代码的编译分为两个阶段。第一阶段是在构建时完成的。处理该阶段的编译器获取源代码并将其转换为“通用中间语言”形式(相同的CIL形式用于VB.NET,F#等 - 因此名称)。源代码中的每个泛型类定义(例如List<T>
)都以CIL形式生成一个类定义。在生成CIL之前,编译器会做出关于将应用哪些函数重载的所有决定。
稍后,当程序运行时,公共语言运行时将不会为程序可能想到的所有类生成代码,而是推迟为每个类生成代码,直到第一次实际使用它为止。在此步骤中,List<int>
之类的内容会生成List<string>
或List<KeyValuePair<Dictionary<int,string>, Tuple<Cat,Dog>>>
的不同机器代码。程序想要使用的一组可能类型不需要限制。可以在C#中合法地使用一种方法,在给定通用T
的参数的情况下,将使用List<T>
调用泛型方法(如果给定List<T>
则会传递List<List<T>>
如果给出会传递List<List<List<T>>>
等等。如果事情嵌套得太深,程序可能会因OutOfMemoryException
或类似的问题而死亡,但与C ++不同,程序可以生成的类型数量不需要在编译时受限;只有当程序试图实际使用太多不同的类型时才会出现问题。
CLR能够在生成代码时进行某些类型的通用替换,但它不处理重载解析(如上所述,在C#到CIL转换步骤中处理)。虽然在CLR中使用重载解析等功能可能会有一些优势,但它也会使CLR变得更加复杂。如果一个特别棘手的过载解决问题需要四分之一秒,这可能不是编译C#代码的问题,但是对于这样的事情停止运行时间为四分之一是不可取的。
答案 2 :(得分:1)
这将调用“非泛型”版本:
public class A<T>
{
public virtual void DoStuff(T value)
{
InternalDoStuff(value);
}
protected void InternalDoStuff(int value)
{
Console.WriteLine("Non-generic version");
}
protected void InternalDoStuff(T value)
{
Console.WriteLine("Generic version");
}
}
public class B : A<int>
{
public override void DoStuff(int value)
{
InternalDoStuff(value);
}
}
答案 3 :(得分:1)
InternalDoStuff
内DoStuff
的调用在A<T>
编译时受到约束。调用来自B
实例的事实不会以任何方式影响重载决策。
在汇编DoStuff
时,有2个InternalDoStuff
成员可供选择
InternalDoStuff(T value)
InternalDoStuff(int value)
DoStuff
方法正在传递T
值,因此int
的重载无效。因此,只有一个适用的成员InternalDoStuff(T)
,编译器会选择这个成员。