为什么这个generics在编译时没有解决?
我有以下代码。 我希望它打印:
A B C DONE
相反它打印
P P P DONE
为什么?
UPDATE
我不是要求解决方案。 我想知道为什么会这样。 我认为generics在编译时得到了解决。 从我可以告诉它应该能够在编译时解决这些正确的方法,但显然它不是,我不明白为什么。 我正在寻找解释原因,而不是解决方案。
这是代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication50 { class Parent { public string FieldName { get; set; } public string Id { get; set; } } class ChildA : Parent { public string FieldValue { get; set; } } class ChildB : Parent { public DateTime? Start { get; set; } public DateTime? End { get; set; } } class ChildC : Parent { public ICollection Values { get; set; } } class Program { void Validate(Parent item) where T : Parent { if (item is T) Validate(item as T); } void Validate(ChildA filter) { Console.WriteLine("A"); } void Validate(ChildB filter) { Console.WriteLine("B"); } void Validate(ChildC filter) { Console.WriteLine("C"); } void Validate(Parent filter) { Console.WriteLine("P"); // do nothing placeholder so the code will compile } ArgumentException Fail(Parent filter, string message) { return new ArgumentException(message, filter.FieldName); } void Run() { var list = new List { new ChildA(), new ChildB(), new ChildC() }; Validate(list[0]); Validate(list[1]); Validate(list[2]); } public static void Main() { new Program().Run(); Console.WriteLine(); Console.WriteLine("DONE"); Console.ReadLine(); } } }
generics是一个运行时概念。 这是它们与C ++模板的主要区别,C ++模板是一个编译时的概念。
在Validate
方法中,即使调用者明确指定, T
在编译时也始终是未知的。 Validate
唯一知道T
是它来自Parent
。
更具体地说,generics不能用于生成代码。 你正在尝试的是在C ++下工作,因为当C ++看到对Validate
的调用时,它实际上重新编译了Validate
,因此模板成为一种代码生成。 在C#下, Validate
只编译一次,因此generics不能用作一种代码生成。
在C ++下,对Validate
的调用将在编译时实例化模板。
在C#下,对Validate
的调用将在运行时实例化generics方法。
重载解析在编译时执行,而不是在运行时执行。
通常的解决方案是在这里使用简单的虚拟调度:
class Parent { public virtual void Validate() { Console.WriteLine("P"); } } class ChildA : Parent { public override void Validate() { Console.WriteLine("A"); } } class ChildB : Parent { public override void Validate() { Console.WriteLine("B"); } } void Run() { var list = new List { new ChildA(), new ChildB() }; list[0].Validate(); // prints "A" list[1].Validate(); // prints "B" }
该项目将始终被validation为父类型。
generics背后的想法是你没有类型特定的代码..因此它的“通用”部分。
您正在限制类的一个分支,在这种情况下,Parent和从它下降的所有内容。 这意味着代码应该执行,就像传入的对象是Parent类型一样。
正如dtb所说,访客模式可以在这里适用。
另一个想法是简单地有一个接口,它定义了每个类必须支持的Validate()方法。 我认为这是一条更好的选择。
假设您可以使用C#4.0,则可以通过使用“dynamic”关键字来破坏C#编译器将执行的静态重载决策。 只需将validationfunction更改为:
void Validate(Parent item) where T : Parent { dynamic dyn = item; if (item is T) Validate(dyn); }
输出将是:
C:\tmp>temp.exe A B C DONE
我刚刚从@ juharr链接到Eric Lippert的博客了解到这一点。 阅读有关msdn上动态类型的更多信息。