首页 > 解决方案 > Box CIL 如何在 .net 内部工作?

问题描述

假设我们有以下 C# 代码:

public static void Main() 
{
   int v = 5;
   Object o = v;
   v = 123;
   Console.WriteLine(v + (Int32) o); // Displays "1235"
}

生成的 IL 代码是:

.locals init ([0]int32 v, [1] object o)

 // Load 5 into v.
 IL_0000: ldc.i4.5
 IL_0001: stloc.0

 // Box v and store the reference pointer in o.    <------first boxing
 IL_0002: ldloc.0
 IL_0003: box [mscorlib]System.Int32
 IL_0008: stloc.1

 // Load 123 into v.
 IL_0009: ldc.i4.s 123
 IL_000b: stloc.0

 // Box v and leave the pointer on the stack for Concat.  <------second boxing
 IL_000c: ldloc.0
 IL_000d: box [mscorlib]System.Int32

 // Unbox o: Get the pointer to the In32's field on the stack.
 IL_0017: ldloc.1
 IL_0018: unbox.any [mscorlib]System.Int32

 // Box the Int32 and leave the pointer on the stack for Concat.   <------third boxing
 IL_001d: box [mscorlib]System.Int32

 // Call Concat.
 IL_0022: call string [mscorlib]System.String::Concat(object, object) 

我们可以看到第一个拳击和第二个拳击的工作原理如下:

  1. 将第一个参数压入v堆栈。

  2. 呼叫boxCIL

所以看起来当box被调用时,需要的“参数”是指向第一个字段的堆栈指针v

第三拳的作用如下:

  1. 前面unbox创建了一个值类型指针,这个值类型指针指向堆上装箱实例的第一个字段,然后这个值类型指针被压入堆栈。

  2. 呼叫boxCIL

所以现在看起来当box被调用时,它首先通过取消引用堆栈指针来检查堆栈指针以获取其内容(指向堆的值类型指针)。

所以我的问题是,boxCIL 是否设计得如此通用,有时它直接读取堆栈指针,而有时它取消引用堆栈指针以获取另一个指针(在我的情况下是指向堆的指针)?

标签: c#.netclrcil

解决方案


拆箱并加载堆栈中的unbox.any值类型(因此它复制它):

来自MSDN

生成的对象引用或值类型被压入堆栈。

当应用于值类型的装箱形式时,unbox.any 指令提取包含在 obj(O 类型)中的值,因此等效于 unbox 后跟 ldobj。

你在想的是unbox指令

unbox 指令将对象引用(类型 O)(值类型的装箱表示)转换为未装箱形式的值类型指针(托管指针,类型 &)。提供的值类型 (valType) 是元数据标记,指示包含在装箱对象中的值类型的类型。

与需要复制值类型以在对象中使用的 Box 不同,unbox 不需要从对象中复制值类型。通常它只是计算已存在于装箱对象内部的值类型的地址。

我什至不知道如何强制编译器使用该unbox指令(从这里阅读它没有在 C# 中使用,或者至少它没有在 2010 年的编译器中使用......我已经做了一些测试,混合ref,装箱和拆箱,我无法强制编译器使用它)

嗯...通过查看ILSpy(他们反编译 C# 代码的专家),似乎它unbox仅用于某些的“私有”实现switch(该switch语句以不同的方式编译,具体取决于数量和它具有的条件类型)。关于的唯一参考unbox是在一个名为MatchLegacySwitchOnStringWithHashtable...的方法中,我会说这个名字很清楚。另一个参考在Unsafe.il文件中……该文件“链接”到Unsafe.NET 类。请参阅此处有关该Unsafe.Unbox<T>方法的建议。该方法已被接受,现在是 .NET 的一部分。相应的C# 代码无法编译:

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T Unbox<T>(object box) where T : struct
{
    return ref (T)box;
}

事实上,通过查看.NET 代码,它可能是作为内在函数实现的。

取消一切.. Charlieface 找到了如何强制使用unbox

public struct MyStruct
{
    public int A;

    public int Test()
    {
        object st2 = new MyStruct();
        int a = ((MyStruct)st2).A;
        return a;
    }
}

该方法Test()编译为:

// Methods
.method public hidebysig 
    instance int32 Test () cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 25 (0x19)
    .maxstack 1
    .locals init (
        [0] valuetype MyStruct
    )

    IL_0000: ldloca.s 0
    IL_0002: initobj MyStruct
    IL_0008: ldloc.0
    IL_0009: box MyStruct
    IL_000e: unbox MyStruct
    IL_0013: ldfld int32 MyStruct::A
    IL_0018: ret
} // end of method MyStruct::Test

一些测试中,我会说该系列中的智能操作码是ldfld:它可以与值类型 ( Test1)、对值类型的引用 ( Test2) 和直接未装箱的值类型 ( Test3) 一起使用。

public struct MyStruct
{
    public int A;

    public int Test1(MyStruct st)
    {
        int a = st.A;
        return a;
    }

    public int Test2(ref MyStruct st)
    {
        int a = st.A;
        return a;
    }

    public int Test3(MyStruct st)
    {
        object st2 = st;
        int a = ((MyStruct)st2).A;
        return a;
    }
}

编译为

.method public hidebysig 
    instance int32 Test1 (
        valuetype MyStruct st
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 7 (0x7)
    .maxstack 8

    IL_0000: ldarg.1
    IL_0001: ldfld int32 MyStruct::A
    IL_0006: ret
} // end of method MyStruct::Test1

.method public hidebysig 
    instance int32 Test2 (
        valuetype MyStruct& st
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 7 (0x7)
    .maxstack 8

    IL_0000: ldarg.1
    IL_0001: ldfld int32 MyStruct::A
    IL_0006: ret
} // end of method MyStruct::Test2

.method public hidebysig 
    instance int32 Test3 (
        valuetype MyStruct st
    ) cil managed 
{
    // Method begins at RVA 0x2058
    // Code size 17 (0x11)
    .maxstack 8

    IL_0000: ldarg.1
    IL_0001: box MyStruct
    IL_0006: unbox MyStruct
    IL_000b: ldfld int32 MyStruct::A
    IL_0010: ret
} // end of method MyStruct::Test3

ldfld操作码始终是相同的,并且它使用两(三)种不同的类型:和int32对 boxed 的引用int32(和对 boxed的引用int32)。


推荐阅读