CIL - Boxing / Unboxing vs. Nullable

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
}
+4
1

# 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).

, NULL, , ( ).

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, NULL.

Nullable unboxed , null. isinst , ( , isinst !!T ), . unbox.any , . NULL, .

# 7 is T t, , NULL, 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
}

.

+1

Source: https://habr.com/ru/post/1675931/


All Articles