Say I have the following code:
void Main()
{
int a = 5;
f1(ref a);
}
public void f1(ref int a)
{
if(a > 7) return;
a++;
f1(ref a);
Co
Your Console.WriteLine(a);
will be executed after recursion is finished. Recursion is finished when value of int becomes 8. And to make it to 8 it has recursion for 3 times. So, after last it will print 8 and then pass control to recursion above which will print 8 again as variable referred has value became 8.
Also check ILDASM output
.method public hidebysig static void f1(int32& a) cil managed
{
// Code size 26 (0x1a)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldind.i4
IL_0002: ldc.i4.7
IL_0003: ble.s IL_0006
IL_0005: ret
IL_0006: ldarg.0
**IL_0007: dup**
IL_0008: ldind.i4
IL_0009: ldc.i4.1
IL_000a: add
IL_000b: stind.i4
IL_000c: ldarg.0
IL_000d: call void ConsoleApplication1.Program::f1(int32&)
IL_0012: ldarg.0
IL_0013: ldind.i4
IL_0014: call void [mscorlib]System.Console::WriteLine(int32)
IL_0019: ret
} // end of method Program::f1
I think you are mistaken in saying the int
parameter is being boxed. From MSDN,
Boxing is the process of converting a value type to the type object or to any interface type implemented by this value type
What you have here is an int
parameter being passed by reference, specifically it's a "value type" being passed by reference.
You can refer to Jon Skeet's excellent explanation on parameter passing for details.
It is not a boxing.
There is clear explanation in MSDN ref keyword documentation:
Do not confuse the concept of passing by reference with the concept of reference types. The two concepts are not the same. A method parameter can be modified by ref regardless of whether it is a value type or a reference type. There is no boxing of a value type when it is passed by reference.
It's easy to create a program that could give different results depending on whether ref int
gets boxed:
static void Main()
{
int a = 5;
f(ref a, ref a);
}
static void f(ref int a, ref int b)
{
a = 3;
Console.WriteLine(b);
}
What do you get? I see 3
printed.
Boxing involves creating copies, so if ref a
were boxed, the output would have been 5
. Instead, both a
and b
are references to the original a
variable in Main
. If it helps, you can mostly (not entirely) think of them as pointers.
Passing a value type by reference causes its position on the stack to get passed rather than the value itself. It has nothing to do with boxing and unboxing. This makes thinking about how the stack looks during the recursive calls rather easy, as every single call refers to the "same" location on the stack.
I think a lot of confusion comes from MSDN's paragraph on boxing and unboxing:
Boxing is name given to the process whereby a value type is converted into a reference type. When you box a variable, you are creating a reference variable that points to a new copy on the heap. The reference variable is an object, ...
Possibly confusing you between two different things: 1) the "converting" as you will, of a value type to say an object, which is by definition a reference type:
int a = 5;
object b = a; // boxed into a reference type
and 2) with the passing of a value type parameter by reference:
main(){
int a = 5;
doWork(ref a);
}
void doWork(ref int a)
{
a++;
}
Which are two different things.
Adding to the existing answers how this is implemented:
The CLR supports so called managed pointers. ref
passes a managed pointer to the variable on the stack. You can also pass heap locations:
var array = new int[1];
F(ref array[0]);
You can also pass references to fields.
This does not result in pinning. Managed pointers are understood by the runtime (in particular by the GC). They are relocatable. They are safe and verifiable.