运行时中的泛型(C# 编程指南)
泛型类型或方法编译为公共中间语言 (CIL) 时,它包含将其标识为具有类型参数的元数据。 如何使用泛型类型的 CIL 根据所提供的类型参数是值类型还是引用类型而有所不同。
使用值类型作为参数首次构造泛型类型时,运行时创建专用的泛型类型,CIL 内的适当位置替换提供的一个或多个参数。 为每个用作参数的唯一值类型一次创建专用化泛型类型。
例如,假定程序代码声明了一个由整数构造的堆栈:
Stack<int>? stack;
此时,运行时生成一个专用版 Stack<T> 类,其中用整数相应地替换其参数。 现在,每当程序代码使用整数堆栈时,运行时都重新使用已生成的专用 Stack<T> 类。 在下面的示例中创建了两个整数堆栈实例,且它们共用 Stack<int>
代码的一个实例:
Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();
但是,假定在代码中另一点上再创建一个将不同值类型(例如 long
或用户定义结构)作为参数的 Stack<T> 类。 其结果是,运行时在 CIL 中生成另一个版本的泛型类型并在适当位置替换 long
。 转换已不再必要,因为每个专用化泛型类本机包含值类型。
对于引用类型,泛型的作用方式略有不同。 首次使用任意引用类型构造泛型类型时,运行时创建一个专用化泛型类型,用对象引用替换 CIL 中的参数。 之后,每次使用引用类型作为参数实例化已构造的类型时,无论何种类型,运行时皆重新使用先前创建的专用版泛型类型。 原因可能在于所有引用大小相同。
例如,假定有两个引用类型、一个 Customer
类和一个 Order
类,并假定已创建 Customer
类型的堆栈:
class Customer { }
class Order { }
Stack<Customer> customers;
此时,运行时生成一个专用版 Stack<T> 类,此类存储之后会被填写的引用类型,而不是存储数据。 假定下一行代码创建另一引用类型的堆栈,其名为 Order
:
Stack<Order> orders = new Stack<Order>();
不同于值类型,不会为 Order
类型创建 Stack<T> 类的另一专用版。 相反,创建专用版 Stack<T> 类的实例并将 orders
变量设置为引用此实例。 假定之后遇到一行创建 Customer
类型堆栈的代码:
customers = new Stack<Customer>();
与之前使用通过 Order
类型创建的 Stack<T> 类一样,会创建专用 Stack<T> 类的另一个实例。 其中包含的指针设置为引用 Customer
类型大小的内存区。 由于引用类型的数量因程序不同而有较大差异,因此通过将编译器为引用类型的泛型类创建的专用类的数量减少至 1,泛型的 C# 实现可极大减少代码量。
此外,使用值类型或引用类型参数实例化泛型 C# 类时,反射可在运行时对其进行查询,且其实际类型和类型参数皆可被确定。