我使用反射来检查System.Int32
的内容,发现它包含另一个System.Int32
。
System.Int32 m_value;
我不明白这是怎么可能的。
此int
实际上是您所拥有的“后退整数”:如果您选中int
并使用反射来更改其m_value
字段的值,则可以有效地更改整数的值:
object testInt = 4;
Console.WriteLine(testInt); // yields 4
typeof(System.Int32)
.GetField("m_value", BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(testInt, 5);
Console.WriteLine(testInt); // yields 5
这个奇点背后必须有一个理性的解释。值类型如何包含自身? CLR使用它有什么神奇之处?
答案 0 :(得分:3)
如上所述,32位整数可以分为两类。内存中的四个字节或CPU寄存器(不仅仅是堆栈),快速版本。它可以嵌入System.Object,盒装版本。 System.Int32的声明与后者兼容。装箱时,它具有典型的对象标题,后跟4个字节,用于存储值。这4个字节完全映射到m_value成员。也许你明白为什么这里没有冲突:m_value 总是快速的非盒装版本。因为没有箱装盒装整数这样的东西。
语言编译器和JIT编译器都非常清楚Int32的属性。编译器负责决定何时需要对整数进行装箱和取消装箱,它会生成相应的IL指令来执行此操作。它知道哪些IL指令可用,允许整数操作而不先装箱。从System.Int32实现的方法中可以明显看出,它没有例如operator ==()的覆盖。这是由CEQ操作码完成的。但是它确实具有Equals()的覆盖,当整数被加框时需要覆盖Object.Equals()方法。您的编译器需要具有相同的意识。
答案 1 :(得分:1)
查看此thread,对这个谜团进行艰苦的讨论。
答案 2 :(得分:0)
魔法实际上是拳击/拆箱。
System.Int32
(及其别名int
)是值类型,这意味着它通常在堆栈上分配。 CLR采用System.Int32
声明,只需将其转换为32位的堆栈空间。
但是,当您编写object testInt = 4;
时,编译器会自动将您的值4
设置为引用,因为object
是引用类型。你所拥有的是一个指向System.Int32
的引用,它现在是堆上某处的32位空间。但是会调用System.Int32
的自动装箱引用(...等待它......)System.Int32
。
您的代码示例正在创建引用 System.Int32
并更改其指向的值 System.Int32
。这解释了奇怪的行为。
答案 3 :(得分:0)
其他答案是无知的和/或误导的。
首先阅读我对How do ValueTypes derive from Object (ReferenceType) and still be ValueTypes?的回答,可以帮助理解这一点
System.Int32
是一个包含32位带符号整数的结构。它不包含自身。
要在IL中引用值类型,语法为valuetype [assembly]Namespace.TypeName
。
II.7.2内置类型 CLI内置类型具有在基类库中定义的相应值类型。仅应使用其特殊编码在签名中引用它们(即,不使用通用值类型TypeReference语法)。分区I指定了内置类型。
这意味着,如果您有一个采用32位整数的方法,则不得使用常规的valuetype [mscorlib]System.Int32
语法,而应使用32位带符号内置整数{{ 1}}。
在C#中,这意味着无论您键入int32
还是System.Int32
,任何一个都将编译为int
,而不是 int32
。
您可能已经听说valuetype [mscorlib]System.Int32
是C#中int
的别名,但实际上两者都是内置CLS值类型{{1 }}。
因此,像这样的结构
System.Int32
确实会编译为(因此无效):
int32
public struct MyStruct
{
internal MyStruct m_value;
}
相反编译为(忽略接口):
.class public sequential ansi sealed beforefieldinit MyStruct extends [mscorlib]System.ValueType
{
.field assembly valuetype MyStruct m_value;
}
C#编译器不需要不需要特殊情况来编译namespace System
{
public struct Int32
{
internal int m_value;
}
}
,因为CLI规范规定,所有对.class public sequential ansi sealed beforefieldinit System.Int32 extends [mscorlib]System.ValueType
{
.field assembly int32 m_value;
}
的引用都将替换为所构建的特殊编码。 -在CLS值类型System.Int32
中。
嗯System.Int32
是一个不包含另一个int32
而是一个System.Int32
的结构。在IL中,可以有2个方法重载,一个重载System.Int32
,另一个重载int32
,并使它们共存:
System.Int32
ILSpy,dnSpy,.NET Reflector等反编译器可能会产生误导。它们(在撰写本文时)将同时int32
和.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89)
.ver 2:0:0:0
}
.assembly test {}
.module test.dll
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003
.corflags 0x00000001
.class MyNamespace.Program
{
.method static void Main() cil managed
{
.entrypoint
ldc.i4.5
call int32 MyNamespace.Program::Lol(valuetype [mscorlib]System.Int32) // Call the one taking the System.Int32 type.
call int32 MyNamespace.Program::Lol(int32) // Call the overload taking the built in int32 type.
call void [mscorlib]System.Console::Write(int32)
call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
pop
ret
}
.method static int32 Lol(valuetype [mscorlib]System.Int32 x) cil managed
{
ldarg.0
ldc.i4.1
add
ret
}
.method static int32 Lol(int32 x) cil managed
{
ldarg.0
ldc.i4.1
add
ret
}
}
反编译为C#关键字int32
或类型System.Int32
,因为这就是我们在C#中定义整数的方式。
但是int
是32位有符号整数的内置值类型(即VES直接支持这些类型,并带有System.Int32
,int32
,{{ 1}}等); add
是在类库中定义的对应值类型。
相应的sub
类型用于装箱,并用于ldc.i4.x
,System.Int32
等方法。
如果您使用纯IL编写程序,则绝对可以以完全相同的方式使自己的值类型包含System.Int32
,在这种情况下,您仍在使用ToString()
,但在自定义“对应”值类型。
CompareTo()
这与int32
类型没有什么不同,除了C#编译器不会将int32
视为对应的.class MyNamespace.Program
{
.method hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 8
ldc.i4.0
call void MyNamespace.Program::PrintWhetherGreaterThanZero(int32)
ldc.i4.m1 // -1
call void MyNamespace.Program::PrintWhetherGreaterThanZero(int32)
ldc.i4.3
call void MyNamespace.Program::PrintWhetherGreaterThanZero(int32)
ret
}
.method private hidebysig static void PrintWhetherGreaterThanZero(int32 'value') cil managed noinlining
{
.maxstack 8
ldarga 0
call instance bool MyCoolInt32::IsGreaterThanZero()
brfalse.s printOtherMessage
ldstr "Value is greater than zero"
call void [mscorlib]System.Console::WriteLine(string)
ret
printOtherMessage:
ldstr "Value is not greater than zero"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
}
.class public MyCoolInt32 extends [mscorlib]System.ValueType
{
.field assembly int32 myCoolIntsValue;
.method public hidebysig bool IsGreaterThanZero()
{
.maxstack 8
ldarg.0
ldind.i4
ldc.i4.0
bgt.s isNonZero
ldc.i4.0
ret
isNonZero:
ldc.i4.1
ret
}
}
类型,而对于CLR而言,这没关系。但是,这将使PEVerify.exe失败,但可以正常运行。
反编译器在对上述内容进行反编译时将显示强制类型转换和明显的指针取消引用,因为它们不认为System.Int32
和MyCoolInt32
两者相关。
但是从功能上讲,没有区别,CLR的幕后也没有魔术。