If I understand how the CLR boxes things and processes nullables, as described in Boxing / Unboxing Nullable Types - Why is this implementation? There is something else that bothers me. For example, the following C # 7 code
void C<T>(object o) where T : struct {
if (o is T t)
Console.WriteLine($"Argument is {typeof(T)}: {t}");
}
compiles to the next 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
but next c #
void D<T>(object o) where T : struct {
if (o is T)
Console.WriteLine($"Argument is {typeof(T)}: {(T) o}");
}
compiles to the next 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
What I think: Looking at the CIL of the first method, it seems that (1) to check whether the argument is [boxed?] By Nullable<T>pushing it on the stack if it is, and nullotherwise, (2) unpacks it (what if it null? ), (3) tries to get its value, default(T)otherwise (4), and then checks whether it matters or not, forks if it is not. The CIL of the second method is quite simple, which simply tries to unpack the argument.
, unboxing Nullable<T>, " "? -, CIL, int, , , , (.. int, Nullable<int>), 't isinst ? Nullable<T> CIL?
: , - MSIL, , object, int, int, Nullable<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
}