c# - 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)
我们可以看到第一个拳击和第二个拳击的工作原理如下:
将第一个参数压入
v
堆栈。呼叫
box
CIL
所以看起来当box
被调用时,需要的“参数”是指向第一个字段的堆栈指针v
。
第三拳的作用如下:
前面
unbox
创建了一个值类型指针,这个值类型指针指向堆上装箱实例的第一个字段,然后这个值类型指针被压入堆栈。呼叫
box
CIL
所以现在看起来当box
被调用时,它首先通过取消引用堆栈指针来检查堆栈指针以获取其内容(指向堆的值类型指针)。
所以我的问题是,box
CIL 是否设计得如此通用,有时它直接读取堆栈指针,而有时它取消引用堆栈指针以获取另一个指针(在我的情况下是指向堆的指针)?
解决方案
拆箱并加载堆栈中的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
)。
推荐阅读
- regex - RegEx - 匹配其他标签之间的特定 html 标签
- http - discord.js http 请求/状态
- python - 在 Python 中嵌套列表
- django - 如何在 Django 中手动添加删除 SQL 约束?
- swift - 有没有办法根据 SWIFT 中的屏幕尺寸自动调整图像或任何内容的大小?
- javascript - 属性图像 div 响应
- javascript - 如何在 BootstrapVue 中发出可以永久关闭的警报?
- ios - 如何将 JSON 数据加载到 FSCalendar Swift 5
- robotframework - Robot Framework - 如果文件存在则运行关键字
- lua - 在roblox studio中按一个按钮和文本增加+1