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

时间:2011-01-20 19:55:09

标签: c# .net struct primitive value-type

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

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

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

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

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位整数?更一般地说,值类型(作为一种值的定义)如何包含类型为本身的字段?它看起来像是turtles all the way down

黑魔法?


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

** Martin v. LöwisEric Lippert都指出,这既不完全准确也不恰当。有关详细信息,请参阅他们的答案。

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

3 个答案:

答案 0 :(得分:11)

据我所知,在一个存储在程序集中的字段签名中,有一些硬编码字节模式表示'核心'基本类型 - 有符号/无符号整数,浮点数(以及字符串,它们是参考类型和特殊情况)。 CLR本身就知道如何处理这些问题。有关签名的位模式,请查看CLR规范的第23部分第23.2.12节。

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

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

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

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

答案 1 :(得分:7)

  

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

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

  

看来我们在这里被骗了

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

  

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

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

  

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

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

答案 2 :(得分:5)

除了thecoop的回答之外,还有三个评论:

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

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

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