From C# to CLR Jitted Code - ByVal and ByRef

From C# to CLR Jitted Code - ByVal and ByRef

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

{

publicclassMyClass

{

publicstaticvoid Main()

{

MyType o1 = newMyType();

MyType o2 = newMyType();

MyType o3;

Test(o1, ref o2, out o3);

}

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

publicstaticvoid Test(MyType o1, refMyType o2, outMyType o3)

{

o1.X = 1;

o2.X = 2;

o3 = newMyType();

o3.X = 3;

}

}

publicclassMyType

{

publicint 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 voidMain() cil managed

{

.entrypoint

// Code size25 (0x19)

.maxstack3

.locals init (class CodeGen.MyType V_0,

class CodeGen.MyType V_1,

class CodeGen.MyType V_2)

IL_0000:nop

IL_0001:newobjinstance void CodeGen.MyType::.ctor()

IL_0006:stloc.0

IL_0007:newobjinstance void CodeGen.MyType::.ctor()

IL_000c:stloc.1

IL_000d:ldloc.0

IL_000e:ldloca.sV_1

IL_0010:ldloca.sV_2

IL_0012:callvoid 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 voidTest(class CodeGen.MyType o1,

class CodeGen.MyType& o2,

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

{

// Code size32 (0x20)

.maxstack8

IL_0000:nop

IL_0001:ldarg.0

IL_0002:ldc.i4.1

IL_0003:stfldint32 CodeGen.MyType::X

IL_0008:ldarg.1

IL_0009:ldind.ref

IL_000a:ldc.i4.2

IL_000b:stfldint32 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:newobjinstance void CodeGen.MyType::.ctor()

IL_0016:stind.ref

IL_0017:ldarg.2

IL_0018:ldind.ref

IL_0019:ldc.i4.3

IL_001a:stfldint32 CodeGen.MyType::X

IL_001f:ret

} // end of method MyClass::Test

CodeGen.MyClass.Main()

Begin 02570070, size 4e

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

02570071 56pushesi

02570072 83ec08subesp,8 // Leave some stack space and use them as local variables

02570075 33c0xoreax,eax

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

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

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