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