C#/ .Net泛型如何知道它们的参数类型? [英] How do C#/.Net generics know their parameter types?

查看:173
本文介绍了C#/ .Net泛型如何知道它们的参数类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在C#中,泛型函数或类意识到其通用参数的类型。这意味着动态类型信息(如)是可用(与Java不同)。

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



如果泛型被保存到IL级别,我相信他们是,那么我想知道这是怎么做到的。

解决方案

由于您已经编辑了您的问题并将其扩展到了C#编译器之外的JIT编译器,因此以下是对该过程的概述,并以 List< T> 作为我们的例子。



正如我们已经确定的那样, List< T> class。该表示具有与C#代码中看到的 T 类型参数相对应的类型参数。正如Holger Thiemann在他的评论中所说的那样,当你使用给定类型参数的 List<>< / code>类时,JIT编译器会为此类创建一个本地代码表示但是,对于引用类型,它仅编译一次本机代码,并将其重用于所有其他引用类型。这是可能的,因为在虚拟执行系统(VES,通常称为运行时)中,只有一个引用类型,称为 O 在规范中(见I.12.1表I.6,标准: http:/ /www.ecma-international.org/publications/standards/Ecma-335.htm )。这种类型被定义为对托管内存的本地大小对象引用。



换句话说,VES(虚拟)评估堆栈中的所有对象都由一个对象引用(实际上是一个指针),它本身是基本上无类型的。 VES如何确保我们不使用不兼容类型的成员?阻止我们调用 System.Random

$ b实例上的 string.Length 属性
$ b

为了强制执行类型安全,VES使用描述每个对象引用的静态类型的元数据,将方法调用的接收方的类型与方法的元数据标记所标识的类型进行比较(这适用于访问例如,要调用对象类的方法,对该对象的引用必须位于虚拟评估堆栈的顶部。

。该引用的静态类型是已知的,这要归功于方法的元数据和堆栈转换分析 - 每条IL指令引起的堆栈状态变化。然后,调用 callvirt 指令通过包含表示方法的元数据令牌来指示要调用的方法,当然表示方法定义的类型。



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

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



一个约束参数类型,该类型的引用可以接收由约束类型定义的方法的调用。例如,如果您编写了一个方法,其中约束类型 T 来自 ICollection ,您可以调用 ICollection.Count 类型为 T 的引用的getter。 VES知道调用这个getter是安全的,因为它可以确保任何引用被存储到堆栈中的那个位置,它将是一个实现 ICollection 接口的类型的实例。无论对象的实际类型是什么,JIT编译器都可以使用相同的本地代码。



还要考虑依赖泛型类型参数的字段。在 List< T> 的情况下,有一个类型为 T [] 的数组,它包含名单。请记住,实际的内存数组将是 O 对象引用的数组。无论数组是否为 List > 的成员,构造该数组的本机代码或读取或写入其元素的本机代码看起来都是相同的, List< FileInfo>



因此,在不受约束的泛型类型的范围内,如 List< T> T 引用与 System.Object 引用。泛型的优点是,VES将类型参数替换为调用者范围中的类型参数。换句话说,即使 List< string> List< FileInfo> ,调用者看到该函数的 Find 方法返回一个 string ,而另一个返回一个 FileInfo



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


In C# a generic function or class is aware of the types of its generic parameters. This means that dynamic type information, like is or as is available (in contrast to Java where it is not).

I'm curious, how does the compiler provides this type information to the generic methods? For classes I can image the instances can simply have a pointer to the type, but for generic functions I'm not sure, perhaps just a hidden parameter?

If the generics are preserved into the IL level, which I believe they are, then I'd like to know how this is done at that level.

解决方案

Since you've edited your question to extend it beyond the C# compiler to the JIT compiler, here's an overview of the process, taking List<T> as our example.

As we've established, there is only one IL representation of the List<T> class. This representation has a type parameter corresponding to the T type parameter seen in C# code. As Holger Thiemann says in his comment, when you use the List<> class with a given type argument, the JIT compiler creates a native-code representation of the class for that type argument.

However, for reference types, it compiles the native code only once and reuses it for all other reference types. This is possible because, in the virtual execution system (VES, commonly called the "runtime"), there is only one reference type, called O in the spec (see paragraph I.12.1, table I.6, in the standard: http://www.ecma-international.org/publications/standards/Ecma-335.htm). This type is defined as a "native size object reference to managed memory."

In other words, all objects in the (virtual) evaluation stack of the VES are represented by an "object reference" (effectively a pointer), which, taken by itself, is essentially typeless. How then does the VES ensure that we don't use members of an incompatible type? What stops us from calling the string.Length property on an instance of System.Random?

To enforce type safety, the VES uses metadata that describes the static type of each object reference, comparing the type of a method call's receiver to the type identified by the method's metadata token (this applies to access of other member types as well).

For example, to call a method of the object's class, the reference to the object must be on the top of the virtual evaluation stack. The static type of this reference is known thanks to the method's metadata and analysis of the "stack transition" -- the changes in the state of the stack caused by each IL instruction. The call or callvirt instruction then indicates the method to be called by including a metadata token representing the method, which of course indicates the type on which the method is defined.

The VES "verifies" the code before compiling it, comparing the reference's type to that of the method. If the types are not compatible, verification fails, and the program crashes.

This works just as well for generic type parameters as it does for non-generic types. To achieve this, the VES limits the methods that can be called on an reference whose type is an unconstrained generic type parameter. The only allowed methods are those defined on System.Object, because all objects are instances of that type.

For a constrained parameter type, the references of that type can receive calls for methods defined by the types of the constraint. For example, if you write a method where you have constrained type T to be derived from ICollection, you can call the ICollection.Count getter on a reference of type T. The VES knows that it is safe to call this getter because it ensures that any reference being stored to that position in the stack will be an instance of some type that implements the ICollection interface. No matter what the actual type of the object is, the JIT compiler can therefore use the same native code.

Consider also fields that depend on the generic type parameter. In the case of List<T>, there is an array of type T[] that holds the elements in the list. Remember that the actual in-memory array will be an array of O object references. The native code to construct that array, or to read or write its elements, looks just the same regardless of whether the array is a member of a List<string> or of a List<FileInfo>.

So, within the scope of an unconstrained generic type such as List<T>, the T references are just as good as System.Object references. The advantage of generics, though, is that the VES substitutes the type argument for the type parameter in the caller's scope. In other words, even though List<string> and List<FileInfo> treat their elements the same internally, the callers see that the Find method of the one returns a string, while that of the other returns a FileInfo.

Finally, because all of this is achieved by metadata in the IL, and because the VES uses the metadata when it loads and JIT-compiles the types, the information can be extracted at run time through reflection.

这篇关于C#/ .Net泛型如何知道它们的参数类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆