C#运行时的泛型

来源:互联网 发布:mac系统word文件消失 编辑:程序博客网 时间:2024/05/21 19:48

C#运行时的泛型

泛型类的编译方法与常规类的编译方法几乎没有差别。事实上,编译结果只不过是元数据和中间语言 (IL)。当然,为了接受代码中用户提供的类型,应对 IL 进行参数化。根据提供的类型参数是值类型还是引用类型,泛型的 IL 的用法会有所不同。

当将值类型作为参数首次构造泛型时,运行时将使用提供的参数替换 IL 中的相应位置来创建一个专用的泛型。针对每个用作参数的唯一值类型,将一次性创建专用的泛型。

例如,假设程序代码声明了一个由整数构造的 Stack:

Stack<int> stack;

此时,运行时将生成一个专用的 Stack 类,并用整数替换此类的相应参数。现在,无论程序代码何时使用整数 Stack,运行时都将重复使用生成的专用 Stack 类。以下示例将创建整数 Stack 的两个实例,每个实例均使用由此整数 Stack 的运行时所生成的代码来创建:

Stack<int> stackOne = new Stack<int>();Stack<int> stackTwo = new Stack<int>();

但是,如果在程序代码中的其他位置又创建了一个 Stack 类,并使用不同的值类型(例如长整型或用户定义的结构)作为其参数,则运行时将生成其他形式的泛型,而这时会替换 IL 相应位置中的长整型参数。为使用值类型构造的泛型创建专用类的优点是可以获得更好的性能。毕竟每个专用的泛型类都是在“本地”包含值类型,因此不必再进行转换。

泛型与引用类型的工作方式稍有不同。首次使用任何引用类型构造泛型时,运行时用对象引用替换 IL 中的参数来创建专用的泛型。之后,每当使用引用类型作为参数实例化构造的类型时,无论构造的是何种类型,运行时都会重复使用先前创建的专用泛型。

例如,假设有两个引用类型,Customer 类和 Order 类,并进一步假设您创建了 Customer 类型的 Stack:

Stack<Customer> customers;

此时,运行时将生成专用 Stack 类,该类并不存储数据,而是存储随后填充的对象引用。假设下一行代码创建了一个其他引用类型的 Stack,称为 Order:

Stack<Order> orders = new Stack<Order>();

与值类型不同,没有为 Order 类型创建另一个专用的 Stack 类,而是创建了专用 Stack 类的实例并设置 orders 变量来引用它。对于替换类型参数的每个对象引用,按照 Order 类型的大小分配内存空间,并将指针设置为引用该内存位置。假设您随后遇到了一行用于创建 Customer 类型的 Stack 的代码:

customers = new Stack<Customer>();

同上一个使用 Order 类型创建的 Stack 类一样,创建了专用 Stack 类的另一个实例,并将其中包含的指针设置为引用 Customer 类型大小的内存区域。由于不同的程序在引用类型的数量上存在着很大差异,因此泛型的 C# 实现通过将引用类型的数量减少到编译器为引用类型的泛型类创建的专用类数量,大大降低了代码的膨胀速度。

此外,当使用类型参数(无论是值类型还是引用类型)实例化泛型 C# 类时,可以在运行时使用反射和实际类型进行查询,并且可以确定其类型参数。

C# 泛型与其他实现之间的差异

C++ 模板与 C# 泛型存在着显著的差别。C# 泛型被编译成 IL,这使得在运行时会智能地为每个值类型创建相应的专用类型,而为引用类型只会创建一次专用类型;C++ 模板实际上是代码扩展宏,它为提供给模板的每个类型参数生成一个专用类型。因此,当 C++ 编译器遇到模板(例如整数 Stack)时,它会将模板代码扩展为 Stack 类并将整数作为该类本身的类型包含在其中。无论类型参数是值类型还是引用类型,如果不专门设计链接器来降低代码膨胀速度,C++ 编译器每次都会创建一个专用类,从而导致比使用 C# 泛型更显著的代码膨胀速度。

而且,C++ 模板不能定义约束。C++ 模板只能通过使用一个成员(可能属于也可能不属于类型参数),隐式定义约束。如果最终传递给泛型类的类型参数中存在该成员,程序将正常运行。否则,程序将失败,并可能返回隐藏的错误信息。由于 C# 泛型可以声明约束,并且具有严格的类型,因此不存在这些潜在的错误。

现在,Sun Microsystems® 已经在新版本的 Java 语言(代码名称为“Tiger”)中添加了其他的泛型。Sun 选择的实现不需要修改 Java 虚拟机。因此,Sun 面临着如何在未修改的虚拟机上实现泛型的问题。

提出的 Java 实现使用与 C++ 中的模板和 C# 中的泛型类似的语法,包括类型参数和约束。然而,由于它处理值类型与处理引用类型的方式不一样,因此未修改的 Java 虚拟机不支持值类型的泛型。因此,Java 中的泛型无法得到有效的执行。事实上,Java 编译器会在需要返回数据时,从指定的约束(如果声明了)或基本对象类型(如果未声明约束)插入自动向下的类型转换。此外,Java 编译器将在运行时生成一个专用类型,然后使用它实例化任何构造类型。最后,由于 Java 虚拟机本身不支持泛型,因此无法在运行时确定泛型实例的类型参数,而且反射的其他用途也会受到严重限制。