I am trying to understand the difference between ByVal and ByRef objects. In below C# code we pass different parameter types to the Test method. Let’s see how the runtime treat them differently at the IL and Assembly level.

 

using System;

using System.Runtime.CompilerServices;

namespace CodeGen

{

    public class MyClass

    {

        public static void Main()

        {

            MyType o1 = new MyType();

            MyType o2 = new MyType();

            MyType o3;          

            Test(o1, ref o2, out o3);

        }

        [MethodImpl(MethodImplOptions.NoInlining)] // Without this attribute, it will be inlined

        public static void Test(MyType o1, ref MyType o2, out MyType o3)

        {

            o1.X = 1;

            o2.X = 2;

            o3 = new MyType();

            o3.X = 3;

        }

    }

 

    public class MyType

    {

        public int X;

    }

}

I highlighted operation that is specific for ByRef object. ByVal object usually can be pushed or popped from stack by one IL opcode. ByRef object is usually accessed indirectly from a managed pointer. That is why we need those stind ldind opcode.

 

.method public hidebysig static void  Main() cil managed

{

  .entrypoint

  // Code size       25 (0x19)

  .maxstack  3

  .locals init (class CodeGen.MyType V_0,

           class CodeGen.MyType V_1,

           class CodeGen.MyType V_2)

  IL_0000:  nop

  IL_0001:  newobj     instance void CodeGen.MyType::.ctor()

  IL_0006:  stloc.0

  IL_0007:  newobj     instance void CodeGen.MyType::.ctor()

  IL_000c:  stloc.1

  IL_000d:  ldloc.0

  IL_000e:  ldloca.s   V_1

  IL_0010:  ldloca.s   V_2

  IL_0012:  call       void CodeGen.MyClass::Test(class CodeGen.MyType,

                                                  class CodeGen.MyType&,

                                                  class CodeGen.MyType&)

  IL_0017:  nop

  IL_0018:  ret

} // end of method MyClass::Main

 

 

.method public hidebysig static void  Test(class CodeGen.MyType o1,

                                           class CodeGen.MyType& o2,

                                           [out] class CodeGen.MyType& o3) cil managed noinlining

{

  // Code size       32 (0x20)

  .maxstack  8

  IL_0000:  nop

  IL_0001:  ldarg.0

  IL_0002:  ldc.i4.1

  IL_0003:  stfld      int32 CodeGen.MyType::X

  IL_0008:  ldarg.1

  IL_0009:  ldind.ref

  IL_000a:  ldc.i4.2

  IL_000b:  stfld      int32 CodeGen.MyType::X

  IL_0010:  ldarg.2                                          //It seems that below code can be optimized by reorder the stind.ref to the end of the method.

  IL_0011:  newobj     instance void CodeGen.MyType::.ctor()

  IL_0016:  stind.ref

  IL_0017:  ldarg.2

  IL_0018:  ldind.ref

  IL_0019:  ldc.i4.3

  IL_001a:  stfld      int32 CodeGen.MyType::X

  IL_001f:  ret

} // end of method MyClass::Test

 

 

CodeGen.MyClass.Main()

Begin 02570070, size 4e

02570070 57              push    edi    // Preserve edi esi, in case it is used in the caller code, since we are going to change both in below code

02570071 56              push    esi   

02570072 83ec08          sub     esp,8    // Leave some stack space and use them as local variables

02570075 33c0            xor     eax,eax

02570077 890424          mov     dword ptr [esp],eax     // set ref o3 to null

0257007a 89442404        mov     dword ptr [esp+4],eax   // set ref o2 to null

0257007e b97c34f301      mov     ecx,1F3347Ch            // !dumptype 1Fee47ch reveals that it is the MethodTable for CodeGen.MyType

02570083 e89c1f9bff      call    01f22024                // Allocate some space for above type

02570088 8bf0            mov     esi,eax                 // o1 -> esi       !do eax   can prove that

0257008a 8bce            mov     ecx,esi                 // ecx represents this pointer

0257008c e807440f59      call    mscorlib_ni!System.Object..ctor() (5b664498)     // Complete object construction

02570091 b97c34f301      mov     ecx,1F3347Ch   

02570096 e8891f9bff      call    01f22024                 

0257009b 8bf8            mov     edi,eax                 // o2 ->edi

0257009d 8bcf            mov     ecx,edi

0257009f e8f4430f59      call    mscorlib_ni!System.Object..ctor() (5b664498)

025700a4 893c24          mov     dword ptr [esp],edi           

025700a7 8d442404        lea     eax,[esp+4]             // ref o3 is still null

025700ab 50              push    eax                     // push ref o3 so that Callee can pick it up

025700ac 8d542404        lea     edx,[esp+4]             // edx = [edi]     ref o2

025700b0 8bce            mov     ecx,esi                 // o1 -> ecx

025700b2 ff15fc32f301    call    dword ptr ds:[1F332FCh] // Stub for Test, since Test is not Jitted yet

025700b8 83c408          add     esp,8                   // clean up stack and restore esi edi

025700bb 5e              pop     esi

025700bc 5f              pop     edi

025700bd c3              ret

 

 

CodeGen.MyClass.Test(CodeGen.MyType, CodeGen.MyType ByRef, CodeGen.MyType ByRef)

025700d2 57              push    edi

025700d3 56              push    esi

025700d4 8b7c240c        mov     edi,dword ptr [esp+0Ch] // ref o3 -> edi

025700d8 c7410401000000  mov     dword ptr [ecx+4],1   // o1 ->ecx     +4 is the offset for the field.  The first 4 bytes represents the method table.

025700df 8b02            mov     eax,dword ptr [edx]   // o2 ->[edx]   o2->eax  ByRef has to be accessed indirectly here 

025700e1 c7400402000000  mov     dword ptr [eax+4],2   // set field again 

025700e8 b97c34f301      mov     ecx,1F3347Ch //CodeGen.MyType)

025700ed e8321f9bff      call    01f22024     // Allocate memory

025700f2 8bf0            mov     esi,eax      // o3 -> esi

025700f4 8bce            mov     ecx,esi

025700f6 e89d430f59      call    mscorlib_ni!System.Object..ctor() (5b664498)

025700fb 8d17            lea     edx,[edi]    // This along with the WriteBarrier makes sure that GC can find the root of o3

025700fd e8eeaa5b5b      call    5db2abf0     // JitHelper for WriteBarrier

02570102 8b07            mov     eax,dword ptr [edi]

02570104 c7400403000000  mov     dword ptr [eax+4],3  // set field for o3

0257010b 5e              pop     esi

0257010c 5f              pop     edi

0257010d c20400          ret     4