C# · 12月 31, 2021

C#/ .Net泛型知道他们的参数类型?

在C#中,通用函数或类知道其通用参数的类型.这意味着动态类型信息就像是可用的(与 Java不同).

我很好奇,编译器如何向通用方法提供这种类型的信息?对于类,我可以映像的实例可以简单地有一个指针的类型,但对于通用函数我不知道,也许只是一个隐藏的参数?

如果仿制药被保留在IL水平,我相信他们是,那么我想知道这是如何做到这一点.

解决方法 由于您已经编辑了您的问题,将其扩展到C#编译器以外的JIT编译器,下面是该过程的概述,将List< T>作为我们的例子.

正如我们所建立的,List< T>中只有一个IL表示类.该表示具有对应于C#代码中看到的T类型参数的类型参数.正如霍尔格·蒂曼在他的评论中所说,当你使用列表<>类具有给定类型的参数,JIT编译器将为该类型参数创建该类的本机代码.

但是,对于引用类型,它仅编译本地代码一次,并将其重用于所有其他引用类型.这是可能的,因为在虚拟执行系统(VES,通常称为“运行时”)中,只有一个引用类型,在规范中称为O(参见第I.12.1节,表I.6中的标准:http://www.ecma-international.org/publications/standards/Ecma-335.htm ).此类型定义为“受管理内存的本机大小对象引用”.

换句话说,VES的(虚拟)评估堆栈中的所有对象都由一个“对象引用”(实际上是一个指针)来表示,它本身就是无形的.那么VES如何确保不使用不兼容类型的成员?什么阻止我们在System.Random的一个实例上调用string.Length属性?

为了实现类型安全性,VES使用描述每个对象引用的静态类型的元数据,将方法调用的接收器的类型与方法的元数据令牌识别的类型进行比较(这也适用于其他成员类型的访问).

例如,要调用对象的类的方法,对对象的引用必须位于虚拟评估堆栈的顶部.由于方法的元数据和“堆栈转换”的分析 – 每个IL指令引起的堆栈状态的变化,这种引用的静态类型是已知的.然后,call或callvirt指令通过包括表示方法的元数据令牌来指示要调用的方法,当然这表示方法被定义的类型.

VES在编译之前验证代码,将引用的类型与方法的类型进行比较.如果类型不兼容,验证失败,程序崩溃.

这对于泛型类型参数也是如此,对于非泛型类型也是如此.为了实现这一点,VES限制了对类型为无约束的通用类型参数的引用可以调用的方法.唯一允许的方法是在System.Object上定义的方法,因为所有对象都是该类型的实例.

对于受约束的参数类型,该类型的引用可以接收由约束类型定义的方法的调用.例如,如果您编写一个方法,其中约束类型T从ICollection派生,则可以在类型T的引用上调用ICollection.Count getter.VES知道可以安全地调用此getter,因为它可以确保在堆栈中存储到该位置的任何引用将是实现ICollection接口的某种类型的实例.无论对象的实际类型如何,JIT编译器可以使用相同的本机代码.

还要考虑依赖于通用类型参数的字段.在List< T>的情况下,存在类型T []的数组,其保存列表中的元素.请记住,实际的内存数组将是一个O对象引用的数组.用于构造该数组的本机代码,或读取或写入它的元素,看起来是一样的,无论数组是否是List< string>的成员.或列表< FileInfo&gt ;. 因此,在诸如List< T>之类的无约束通用类型的范围内,T引用与System.Object引用一样好.泛型的优点在于VES将type参数替换为调用者范围中的type参数.换句话说,即使List< string>列表< FileInfo>在内部处理它们的元素,调用者看到一个Find方法返回一个字符串,而另一个返回一个FileInfo.

最后,因为所有这些都是通过IL中的元数据实现的,并且因为VES在加载时使用元数据并且JIT编译类型,所以可以在运行时通过反射来提取信息.