Y. Ivan
Y. Ivan

Reputation: 109

Ref and OUT behind the scenes

I'm wondering to know how ref and out keywords work behind the scenes? For example in case we use it on method will it put this value type variable into some some class in order to work with it as with reference type?

Upvotes: 1

Views: 203

Answers (2)

SᴇM
SᴇM

Reputation: 7213

For the ref you can look at IL code generated for following code:

public static void Main(string[] args)
{
    int x = 5;
    A(ref x);
}

private static void A(ref int x)
{
    x = 10;
}

you will see following:

.method public hidebysig static void  Main(string[] args) cil managed
{
    . . .
    IL_0001:  ldc.i4.5     //Push 5 onto the stack as int32.
    IL_0002:  stloc.0      //Pop a value from stack into local variable 0.
    IL_0003:  ldloca.s   x //Load address of local variable with index indx, short form.
    IL_0005:  call       void ConsoleApp1.Program::A(int32&) //Calls method A() by passing x's address to it
    . . . 
}

and the method A():

.method private hidebysig static void  A(int32& x) cil managed //x's address as argument
{
    // Code size       6 (0x6)
   .maxstack  8
   IL_0000:  nop
   IL_0001:  ldarg.0 //Load argument 0 onto the stack.
   IL_0002:  ldc.i4.s   10 //Push 10 onto the stack as int32, short form.
   IL_0004:  stind.i4 //Store value of type int32 into memory at address
   IL_0005:  ret
}

so basically it passes xs address to method A(), inside which the value of 10 will be written into memory with given address.

and finally for the out:

public static void Main(string[] args)
{
    A(out int x);
}

private static void A(out int x)
{
    x = 10;
}

the IL code would be:

.method private hidebysig static void  A([out] int32& x) cil managed
{
    . . .
    IL_0001:  ldloca.s   x //Load address of local variable with index indx, short form.
    IL_0003:  call       void ConsoleApp1.Program::A(int32&) //Calls method A() by passing x's address to it
    . . .
}

and the method A():

.method private hidebysig static void  A([out] int32& x) cil managed
{
  // Code size       6 (0x6)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldc.i4.s   10
  IL_0004:  stind.i4
  IL_0005:  ret
} // end of method Program::A

as you can see only difference in A(ref int x) and A(out int x) methods is [out] method parameter attribute which will indicate that the parameter passed by out (not ref).

Upvotes: 1

Marc Gravell
Marc Gravell

Reputation: 1062640

in order to work with it as with reference type?

Firstly, you need to understand that "by reference" (ref/out and now in) is not the same thing as "reference type".

Ultimately, when you see:

void SomeMethod(ref int x);

here x is a reference, aka a "managed pointer" to a value; the caller supplies the reference. So, when you have:

int a = 123;
SomeMethod(ref a);

this works by taking the address of the variable a (usually via the ldloca or ldflda IL instruction), and passing that address in as the parameter value.

If the body of SomeMethod is:

void SomeMethod(ref int x) {
    Write(x);
    x = 12;
}

then the Write(x) dereferences the pointer x - it loads the value of x, looks at where it points, and gets an actual value from there. Likewise, the x=12 takes the value 12, looks at the pointer x and writes 12 to where-ever that pointer points.

To write the equivalent in unsafe C# with C# pointers (aka "unmanaged pointers") to show the difference:

void SomeMethod(int* x) {
    Write(*x);
    *x = 12;
}
int a = 123;
SomeMethod(&a);

out is just a fancy version of ref with certain "definite assignment" conventions; in is similarly a fancy version of ref that allows readonly types to work reliably and efficiently while avoiding stack copies.

Upvotes: 3

Related Questions