gpt4 book ai didi

c# - CIL - 装箱/拆箱与可空

转载 作者:行者123 更新时间:2023-11-30 12:39:53 25 4
gpt4 key购买 nike

如果我理解 CLR 装箱和处理可空值的方式,如 Boxing / Unboxing Nullable Types - Why this implementation? 中所述,我仍然有一些困惑。例如,下面的 C# 7 代码

void C<T>(object o) where T : struct {
if (o is T t)
Console.WriteLine($"Argument is {typeof(T)}: {t}");
}

编译成下面的CIL

IL_0000: ldarg.0
IL_0001: isinst valuetype [mscorlib]System.Nullable`1<!!T>
IL_0006: unbox.any valuetype [mscorlib]System.Nullable`1<!!T>
IL_000b: stloc.1
IL_000c: ldloca.s 1
IL_000e: call instance !0 valuetype [mscorlib]System.Nullable`1<!!T>::GetValueOrDefault()
IL_0013: stloc.0
IL_0014: ldloca.s 1
IL_0016: call instance bool valuetype [mscorlib]System.Nullable`1<!!T>::get_HasValue()
IL_001b: brfalse.s IL_003c

IL_001d: ldstr "Argument is {0}: {1}"
IL_0022: ldtoken !!T
IL_0027: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_002c: ldloc.0
IL_002d: box !!T
IL_0032: call string [mscorlib]System.String::Format(string, object, object)
IL_0037: call void [mscorlib]System.Console::WriteLine(string)

IL_003c: ret

还有下面的 C#

void D<T>(object o) where T : struct {
if (o is T)
Console.WriteLine($"Argument is {typeof(T)}: {(T) o}");
}

编译成下面的CIL

IL_0000: ldarg.0
IL_0001: isinst !!T
IL_0006: brfalse.s IL_002c

IL_0008: ldstr "Argument is {0}: {1}"
IL_000d: ldtoken !!T
IL_0012: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_0017: ldarg.0
IL_0018: unbox.any !!T
IL_001d: box !!T
IL_0022: call string [mscorlib]System.String::Format(string, object, object)
IL_0027: call void [mscorlib]System.Console::WriteLine(string)

IL_002c: ret

认为正在发生什么:查看第一种方法的 CIL,似乎 (1) 检查参数是否为 [boxed?] Nullable<T> ,如果是,则将其压入堆栈,否则为 null,(2) 将其拆箱(如果是 null 怎么办?),(3) 尝试获取其值,否则为 default(T),(4) 然后检查它是否具有值与否,如果没有,则分支出去。第二种方法的 CIL 非常简单,它只是尝试对参数进行拆箱。

如果两种代码的语义相同,为什么前一种情况涉及拆箱到 Nullable<T> 而前一种情况“只是拆箱”?其次,在第一个 CIL 中,如果对象参数是一个盒装的 int ,我目前认为这正是它在 jar 上所说的(即盒装 int 而不是盒装 Nullable<int> ),那么 isinst 指令不会总是失败? Nullable<T> 是否在 CIL 级别得到特殊处理?

更新:在手写一些 MSIL 之后,似乎 object 确实是一个装箱的 int ,可以拆箱为 intNullable<int>

.method private static void Foo(object o) cil managed {
.maxstack 1
ldarg.0
isinst int32
brfalse.s L_00
ldarg.0
unbox.any int32
call void [mscorlib]System.Console::WriteLine(int32)
L_00:
ldarg.0
isinst valuetype [mscorlib]System.Nullable`1<int32>
brfalse.s L_01
ldarg.0
unbox valuetype [mscorlib]System.Nullable`1<int32>
call instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault()
call void [mscorlib]System.Console::WriteLine(int32)
L_01:
ldarg.0
unbox valuetype [mscorlib]System.Nullable`1<int32>
call instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue()
brtrue.s L_02
ldstr "No value!"
call void [mscorlib]System.Console::WriteLine(string)
L_02:
ret
}

最佳答案

C# 7 中的新语法同时进行类型检查和类型转换。在旧版本中,这通常以两种可能的方式完成。

if(o is T)
//use (T)o

T t = o as T;
if(t != null)
//use t

对于引用类型,第一个有多余的转换,因为 is 被编译为 isinst 和一个条件分支,从你使用的 CIL 指令可以看出。第二个代码在 CIL 方面与第一个相同,减去了额外的 (T)o 转换(编译为 castclass)。

对于值类型,第二个选项只能用可空类型来完成,我也认为它实际上比第一个要慢一些(必须创建一个结构)。

我已将以下方法编译为 CIL:

static void C<T>(object o) where T : struct
{
T? t = o as T?;
if(t != null)
Console.WriteLine("Argument is {0}: {1}", typeof(T), t);
}

生成此代码:

.method private hidebysig static void  C<valuetype .ctor ([mscorlib]System.ValueType) T>(object o) cil managed
{
// Code size 48 (0x30)
.maxstack 3
.locals init (valuetype [mscorlib]System.Nullable`1<!!T> V_0)
IL_0000: ldarg.0
IL_0001: isinst valuetype [mscorlib]System.Nullable`1<!!T>
IL_0006: unbox.any valuetype [mscorlib]System.Nullable`1<!!T>
IL_000b: stloc.0
IL_000c: ldloca.s V_0
IL_000e: call instance bool valuetype [mscorlib]System.Nullable`1<!!T>::get_HasValue()
IL_0013: brfalse.s IL_002f
IL_0015: ldstr "Argument is {0}: {1}"
IL_001a: ldtoken !!T
IL_001f: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_0024: ldloc.0
IL_0025: box valuetype [mscorlib]System.Nullable`1<!!T>
IL_002a: call void [mscorlib]System.Console::WriteLine(string,
object,
object)
IL_002f: ret
}

除了调用 GetValueOrDefault 之外,这正是问题中的代码,因为我没有获得可空实例的实际值。

可空类型不能直接装箱或拆箱,只能通过其基础值或作为普通空值。第一个 isinst 确保其他类型不会产生异常(我想 isinst !!T 也可以使用),只是一个空引用。 unbox.any 操作码然后根据引用形成一个可为空的实例,然后照常使用。该指令也可以写成空检查并自行形成可为空的实例,但这样会更短。

对于 is T,C# 7 使用第二种方式,因此如果 T 是值类型,它除了使用可空类型别无选择。为什么不选择前一个选项?我只能猜测它在语义或实现、变量分配等方面可能会有一些实质性差异。因此,他们选择与新构造的实现保持一致。

为了比较,这是当我在上面的方法中将 T : struct 更改为 T : class 时产生的结果(以及 T?T):

.method private hidebysig static void  C<class T>(object o) cil managed
{
// Code size 47 (0x2f)
.maxstack 3
.locals init (!!T V_0)
IL_0000: ldarg.0
IL_0001: isinst !!T
IL_0006: unbox.any !!T
IL_000b: stloc.0
IL_000c: ldloc.0
IL_000d: box !!T
IL_0012: brfalse.s IL_002e
IL_0014: ldstr "Argument is {0}: {1}"
IL_0019: ldtoken !!T
IL_001e: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_0023: ldloc.0
IL_0024: box !!T
IL_0029: call void [mscorlib]System.Console::WriteLine(string,
object,
object)
IL_002e: ret
}

再次与原始方法相当一致。

关于c# - CIL - 装箱/拆箱与可空,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43684315/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com