Search code examples
c#.net-corecil.net-7.0

How to detect boxing in IL?


I am trying to figure out whether a boxing occurs when you access an integer as a property of dynamic type

Here is the C# code:

using System.Dynamic;

dynamic o = new ExpandoObject();
int i = 2;

o.Int = 1;

i = o.Int;

Console.WriteLine(o.Int);
Console.WriteLine(i);

Here is what I get from ILSpy:

.method private hidebysig static 
    void '<Main>$' (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x206c
    // Header size: 12
    // Code size: 428 (0x1ac)
    .maxstack 11
    .entrypoint
    .locals init (
        [0] object o,
        [1] int32 i
    )

    // dynamic val = new ExpandoObject();
    IL_0000: newobj instance void [System.Linq.Expressions]System.Dynamic.ExpandoObject::.ctor()
    IL_0005: stloc.0
    // int num = 2;
    IL_0006: ldc.i4.2
    IL_0007: stloc.1
    // (no C# code)
    IL_0008: ldsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`4<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, int32, object>> Program/'<>o__0'::'<>p__0'
    IL_000d: brfalse.s IL_0011

    IL_000f: br.s IL_004a

    IL_0011: ldc.i4.0
    IL_0012: ldstr "Int"
    IL_0017: ldtoken Program
    IL_001c: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
    IL_0021: ldc.i4.2
    IL_0022: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
    IL_0027: dup
    IL_0028: ldc.i4.0
    IL_0029: ldc.i4.0
    IL_002a: ldnull
    IL_002b: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
    IL_0030: stelem.ref
    IL_0031: dup
    IL_0032: ldc.i4.1
    IL_0033: ldc.i4.3
    IL_0034: ldnull
    IL_0035: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
    IL_003a: stelem.ref
    IL_003b: call class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::SetMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, class [System.Runtime]System.Type, class [System.Runtime]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
    IL_0040: call class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`4<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, int32, object>>::Create(class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSiteBinder)
    IL_0045: stsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`4<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, int32, object>> Program/'<>o__0'::'<>p__0'

    // val.Int = 1;
    IL_004a: ldsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`4<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, int32, object>> Program/'<>o__0'::'<>p__0'
    IL_004f: ldfld !0 class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`4<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, int32, object>>::Target
    IL_0054: ldsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`4<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, int32, object>> Program/'<>o__0'::'<>p__0'
    IL_0059: ldloc.0
    IL_005a: ldc.i4.1
    IL_005b: callvirt instance !3 class [System.Runtime]System.Func`4<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, int32, object>::Invoke(!0, !1, !2)
    // (no C# code)
    IL_0060: pop
    IL_0061: ldsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, int32>> Program/'<>o__0'::'<>p__2'
    IL_0066: brfalse.s IL_006a

    IL_0068: br.s IL_008e

    IL_006a: ldc.i4.0
    IL_006b: ldtoken [System.Runtime]System.Int32
    IL_0070: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
    IL_0075: ldtoken Program
    IL_007a: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
    IL_007f: call class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::Convert(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, class [System.Runtime]System.Type, class [System.Runtime]System.Type)
    IL_0084: call class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, int32>>::Create(class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSiteBinder)
    IL_0089: stsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, int32>> Program/'<>o__0'::'<>p__2'

    // num = val.Int;
    IL_008e: ldsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, int32>> Program/'<>o__0'::'<>p__2'
    IL_0093: ldfld !0 class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, int32>>::Target
    IL_0098: ldsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, int32>> Program/'<>o__0'::'<>p__2'
    IL_009d: ldsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, object>> Program/'<>o__0'::'<>p__1'
    IL_00a2: brfalse.s IL_00a6

    IL_00a4: br.s IL_00d5

    IL_00a6: ldc.i4.0
    IL_00a7: ldstr "Int"
    IL_00ac: ldtoken Program
    IL_00b1: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
    IL_00b6: ldc.i4.1
    IL_00b7: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
    IL_00bc: dup
    IL_00bd: ldc.i4.0
    IL_00be: ldc.i4.0
    IL_00bf: ldnull
    IL_00c0: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
    IL_00c5: stelem.ref
    IL_00c6: call class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::GetMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, class [System.Runtime]System.Type, class [System.Runtime]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
    IL_00cb: call class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, object>>::Create(class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSiteBinder)
    IL_00d0: stsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, object>> Program/'<>o__0'::'<>p__1'

    IL_00d5: ldsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, object>> Program/'<>o__0'::'<>p__1'
    IL_00da: ldfld !0 class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, object>>::Target
    IL_00df: ldsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, object>> Program/'<>o__0'::'<>p__1'
    IL_00e4: ldloc.0
    IL_00e5: callvirt instance !2 class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, object>::Invoke(!0, !1)
    IL_00ea: callvirt instance !2 class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, int32>::Invoke(!0, !1)
    IL_00ef: stloc.1
    // (no C# code)
    IL_00f0: ldsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Action`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, class [System.Runtime]System.Type, object>> Program/'<>o__0'::'<>p__4'
    IL_00f5: brfalse.s IL_00f9

    IL_00f7: br.s IL_0138

    IL_00f9: ldc.i4 256
    IL_00fe: ldstr "WriteLine"
    IL_0103: ldnull
    IL_0104: ldtoken Program
    IL_0109: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
    IL_010e: ldc.i4.2
    IL_010f: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
    IL_0114: dup
    IL_0115: ldc.i4.0
    IL_0116: ldc.i4.s 33
    IL_0118: ldnull
    IL_0119: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
    IL_011e: stelem.ref
    IL_011f: dup
    IL_0120: ldc.i4.1
    IL_0121: ldc.i4.0
    IL_0122: ldnull
    IL_0123: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
    IL_0128: stelem.ref
    IL_0129: call class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, class [System.Runtime]System.Collections.Generic.IEnumerable`1<class [System.Runtime]System.Type>, class [System.Runtime]System.Type, class [System.Runtime]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
    IL_012e: call class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Action`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, class [System.Runtime]System.Type, object>>::Create(class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSiteBinder)
    IL_0133: stsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Action`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, class [System.Runtime]System.Type, object>> Program/'<>o__0'::'<>p__4'

    // Console.WriteLine(val.Int);
    IL_0138: ldsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Action`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, class [System.Runtime]System.Type, object>> Program/'<>o__0'::'<>p__4'
    IL_013d: ldfld !0 class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Action`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, class [System.Runtime]System.Type, object>>::Target
    IL_0142: ldsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Action`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, class [System.Runtime]System.Type, object>> Program/'<>o__0'::'<>p__4'
    IL_0147: ldtoken [System.Console]System.Console
    IL_014c: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
    IL_0151: ldsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, object>> Program/'<>o__0'::'<>p__3'
    IL_0156: brfalse.s IL_015a

    IL_0158: br.s IL_0189

    IL_015a: ldc.i4.0
    IL_015b: ldstr "Int"
    IL_0160: ldtoken Program
    IL_0165: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
    IL_016a: ldc.i4.1
    IL_016b: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
    IL_0170: dup
    IL_0171: ldc.i4.0
    IL_0172: ldc.i4.0
    IL_0173: ldnull
    IL_0174: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
    IL_0179: stelem.ref
    IL_017a: call class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::GetMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, class [System.Runtime]System.Type, class [System.Runtime]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
    IL_017f: call class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, object>>::Create(class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSiteBinder)
    IL_0184: stsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, object>> Program/'<>o__0'::'<>p__3'

    IL_0189: ldsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, object>> Program/'<>o__0'::'<>p__3'
    IL_018e: ldfld !0 class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, object>>::Target
    IL_0193: ldsfld class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, object>> Program/'<>o__0'::'<>p__3'
    IL_0198: ldloc.0
    IL_0199: callvirt instance !2 class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, object>::Invoke(!0, !1)
    // (no C# code)
    IL_019e: callvirt instance void class [System.Runtime]System.Action`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, class [System.Runtime]System.Type, object>::Invoke(!0, !1, !2)
    // Console.WriteLine(num);
    IL_01a3: nop
    IL_01a4: ldloc.1
    IL_01a5: call void [System.Console]System.Console::WriteLine(int32)
    // }
    IL_01aa: nop
    IL_01ab: ret
} // end of method Program::'<Main>$'

Solution

  • The best way to know something is to measure, so I've wrote a benchmark. It will measure allocation to dynamic compared to explicit boxing.

    using System.Dynamic;
    using BenchmarkDotNet.Attributes;
    using BenchmarkDotNet.Running;
    
    BenchmarkRunner.Run<MyBench>();
    
    [MemoryDiagnoser]
    public class MyBench
    {
        private dynamic _myObj = new ExpandoObject();
    
        [Benchmark]
        [Arguments(1)]
        public void SetInt(int x)
        {
            _myObj.Int = x;
        }
    
        [Benchmark(Baseline = true)]
        [Arguments(1)]
        public object BoxInt(int x)
        {
            return x;
        }
    }
    

    Which gives following result:

    BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19044.2728/21H2/November2021Update)
    AMD Ryzen 5 3600X, 1 CPU, 12 logical and 6 physical cores
    .NET SDK=7.0.100
      [Host]     : .NET 7.0.0 (7.0.22.51805), X64 RyuJIT AVX2
      DefaultJob : .NET 7.0.0 (7.0.22.51805), X64 RyuJIT AVX2
    
    Method x Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
    SetInt 1 100.150 ns 0.2788 ns 0.2608 ns 35.81 0.15 0.0029 24 B 1.00
    BoxInt 1 2.796 ns 0.0105 ns 0.0098 ns 1.00 0.00 0.0029 24 B 1.00

    As you can see, both methods allocate same way - 24 bytes per method call. I bet int gets boxed somewhere inside this machinery