如何非递归地定义“原始”类型?

由于C#中的struct由其成员的位组成,因此您不能拥有包含任何T字段的值类型T

 // Struct member 'T.m_field' of type 'T' causes a cycle in the struct layout struct T { T m_field; } 

我的理解是上述类型的实例永远不能被实例化* – 任何尝试这样做会导致实例化/分配的无限循环(我猜这会导致堆栈溢出? ** ) – 或者,另外,另一个看待它的方式可能是定义本身没有意义; 也许这是一个弄巧成拙的实体,有点像“这句话是假的”。

但奇怪的是,如果你运行这段代码:

 BindingFlags privateInstance = BindingFlags.NonPublic | BindingFlags.Instance; // Give me all the private instance fields of the int type. FieldInfo[] int32Fields = typeof(int).GetFields(privateInstance); foreach (FieldInfo field in int32Fields) { Console.WriteLine("{0} ({1})", field.Name, field.FieldType); } 

…你会得到以下输出:

 m_value(System.Int32)

看来我们正在“骗”到这里***。 显然我理解像intdouble等原始类型必须以某种特殊的方式在C#的内部深处定义(你无法根据该系统定义系统中的每个可能的单元……你能吗? -不同的话题,不管!); 我只是想知道这里发生了什么

System.Int32类型(例如)如何实际考虑存储32位整数? 更一般地说,值类型(作为一种值的定义)如何包含其类型本身的字段? 它看起来像乌龟一直在下降 。

黑魔法?


*单独注意:这是值类型(“实例化”)的正确单词吗? 我觉得它带有“类似参考”的内涵; 但也许那就是我。 此外,我觉得我之前可能会问过这个问题 – 如果是这样的话,我会忘记人们回答的问题。

** Martinv.Löwis和Eric Lippert都指出,这既不完全准确也不恰当。 查看他们的答案以获取更多信息

***好吧,我发现没人在说谎。 我并不是故意暗示我认为这是假的 ; 我的怀疑是,它在某种程度上过于简单化了。 在了解了(我认为 ) thecoop的答案之后 ,它对我来说更有意义。

据我所知,在存储在程序集中的字段签名中,存在某些硬编码字节模式,表示“核心”基本类型 – 有符号/无符号整数,浮点数(以及字符串,它们是引用类型和一个特例)。 CLR本身就知道如何处理这些问题。 查看CLR规范的第II部分第23.2.12节,了解签名的位模式。

在BCL中的每个原始结构( [mscorlib]System.Int32[mscorlib]System.Single等)中是该本机类型的单个字段,并且因为结构与其组成字段的大小完全相同,所以每个基本结构都是与内存中的本机类型相同的位模式,因此可以通过CLR,C#编译器或使用这些类型的库来解释。

从C#, intdouble等是mscorlib结构的同义词,每个结构都具有CLR本机识别的原始字段。

(这里有一个额外的复杂性,因为CLR规范指定任何具有“短格式”(本机CLR类型)的类型总是必须编码为该缩写forms( int32 ),而不是valuetype [mscorlib]System.Int32 。所以C#编译器也知道了原始类型,但是我不确定在C#编译器和CLR中发生的确切语义和特殊shell,比如对原始结构的方法调用)

因此,由于Godel的不完备性定理,必须有一个“外部”系统可以定义它。 这是魔术,它允许CLR将4个字节解释为本机int32[mscorlib]System.Int32的实例,它是来自C#的别名。

我的理解是,上述类型的实例永远不会被实例化,任何尝试这样做都会导致实例化/分配的无限循环(我猜这会导致堆栈溢出?) – 或者,另外一种看待它的方式可能是定义本身没有意义;

这不是表征情况的最佳方式。 查看它的更好方法是每个结构的大小必须明确定义 。 确定T大小的尝试进入无限循环,因此T的大小没有明确定义。 因此,它不是一个合法的结构,因为每个结构必须具有明确定义的大小。

看来我们在这里撒谎了

没有谎言。 int是包含int类型字段的结构。 int具有已知大小; 根据定义,它是四个字节。 因此它是一个合法的结构,因为它的所有字段的大小都是已知的。

System.Int32类型(例如)如何实际存储32位整数值

类型没有任何事情。 这种类型只是一个抽象概念。 执行存储的是CLR ,它通过在堆上,堆栈上或寄存器中分配四个字节的空间来实现。 如果不是在四个字节的内存中,你怎么想存储一个四字节整数呢?

用typeof(int)引用的System.Type对象如何呈现自身,好像这个值本身就是一个类型为System.Int32的日常实例字段?

这只是一个对象,用任何其他对象编写代码。 这没什么特别的。 你在它上面调用方法,它返回更多的对象,就像世界上的每个其他对象一样。 为什么你认为它有什么特别的东西?

三个评论,除了thecoop的回答:

  1. 你断言递归结构本身不起作用并不完全正确。 它更像是一个声明“这句话是真的”:如果是这样的话就是如此。 有一个类型T,其唯一的成员是T类型是合理的:例如,这样的实例可能消耗0个字节(因为它的唯一成员消耗0个字节)。 如果您有第二个成员(这是不允许它们的原因),递归值类型只会停止工作。

  2. 看看Mono对Int32的定义 。 如您所见:它实际上一个包含自身的类型(因为int只是C#中Int32的别名)。 涉及肯定有“黑魔法”(即特殊shell),正如评论所解释的那样:运行时将按名称查找字段,并且只是期望它在那里 – 我还假设C#编译器将特殊情况下存在在那里。

  3. 在PE程序集中,类型信息通过“类型签名blob”表示。 这些是类型声明的序列,例如用于方法签名,但也用于字段。 这种签名中可用的基元类型列表在CLR规范的第22.1.15节中定义; 允许值的副本位于CorElementType枚举中 。 显然,reflectionAPI将这些原始类型映射到它们对应的System.XYZ值类型。