如何实例化泛型类型



我正在阅读Herbert Schildt的C#完整参考书。他说,关于仿制药,

型类只有一个版本可以处理所有 类型参数为引用类型的情况。这是因为 所有引用的大小(以字节为单位)都相同。因此,只有一个 版本是处理所有类型的引用所必需的。此优化 还可以减少代码膨胀。

有人可以解释为什么所有引用类型只有一个版本的泛型类吗?C# 如何仅使用一个版本进行管理?例如:在值类型的情况下,书中说,C#为Generic<int>Generic<double>创建了一个单独的类。那么,为什么它不为Generic<MyClass>Generic<AnotherClass>创建单独的类呢?泛型引用类型的非创建单独类与类的大小有什么关系(如文中所述)?

首先,正如我在评论中指出的那样:买一本更好的书。

其次,您需要了解如何为泛型生成代码。C# 编译器为每个泛型类生成 IL(一种"中间语言"),并且该 IL 与原始源代码一样,是真正的泛型。在运行时,抖动("实时"编译器)为您调用的每个方法生成机器代码;它将 IL 转换为正在运行的任何计算机的代码。

假设您有一个带有方法的泛型类:

class C<T> 
{
public static T[] One(T item) 
{
return new T[] { item };
}
}

请注意,无论运行时的 T 是什么,此方法都是类型安全的。

调用C<string>.One("hello");时,抖动会生成一个代码,该代码创建一个字符串引用的数组,将给定的字符串引用复制到数组中的第一个位置,并返回对该数组的引用。

现在假设您调用C<string>.One("goodbye").抖动不需要重新生成代码,因为它已经在内存中具有机器代码。

现在,如果您调用C<Task<Giraffe>>.One(giraffeTask),抖动再次不需要重新生成代码,因为它在内存中已经有一个方法,该方法创建一个引用大小的事物的数组,将引用item复制到数组中,并返回对该数组的引用。除了 T[] 创建之外,该方法没有任何地方使用这样一个事实,即 T 实际上是一个字符串或实际上是一个产生长颈鹿的任务。 如果我们确保数组构造函数也不会利用知道类型是什么的优势 - 以及为什么要这样做?-- 然后我们可以重用生成的代码。

但是我们不能在调用C<double>.One时重用为C<int>.One生成的代码,因为第一个将 4 字节整数复制到数组中;第二个复制 8 字节的双精度。抖动可以通过占用额外的空间来制作两个略有不同的机器代码版本,从而更好地优化代码。

此优化是一个实现细节,但它是有助于了解的细节之一,因为它为您提供了一个很好的心智模型,说明该功能的工作原理、为什么以这种方式工作以及它与不同语言中的类似功能有何不同。Java(带擦除)和C++(带模板)都与具有真正运行时泛型类型系统的 C# 略有不同,这会导致语义和性能差异。

我会说引用的文本不太准确。作者似乎试图解释,对于引用类型,类的代码只需要存在一次。实际上,您只能获得该类的一个副本,这是不正确的。

对于值类型,类中给定类型参数的元素所需的存储可能因类型而异,因为值类型的变量是实际存储数据的位置。这意味着必须为所使用的每个值类型重新编译泛型代码,以确保使用正确的存储大小。

对于引用类型,所需的存储只是一个引用。实际数据存储在其他地方(即堆中),无论类型如何,引用的大小都是相同的。因此,一个版本的代码就足够了。

正是在这个意义上,引用的文本是正确的。

但是:泛型类型仍然知道它们的类型参数,运行时仍然对值进行运行时类型检查。此外,具有静态字段的类将需要用于该类的每个类型参数组合的这些字段的新实例。所以我不会说泛型类的"只有一个版本">是真的,即使对于引用类型也是如此。

最新更新