Out, Out, Damn Ref
What is the difference between the out and ref keywords in C#? Rick and I were discussing this earlier today and I decided that I wanted to find out for real. Inherently, I know what the difference is: ref is passed by reference and out is pass by reference with the added requirement that the parameter must be assigned to.
But this is a very minor semantic difference, so the real question is whether or not the difference is enforced by the language/compiler or by the CLR. For this, we will determine the answer empirically with this chunk of code:
private void SetMyFriend(out int x)
{
x = 5;
}
private void TouchMyFriend(ref int x)
{
x = 3;
}
private void MyFriend()
{
int x;
SetMyFriend(out x);
TouchMyFriend(ref x);
}
Once this is written and compiled, we can pull it up with ildasm and have a look at what the compiler has done.
Here is the IL for SetMyFriend()
.method private hidebysig instance void SetMyFriend([out] int32& x) cil managed
{
// Code size 5 (0x5)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.1 // put address of x on the stack
IL_0002: ldc.i4.5 // put a 5 on the stack
IL_0003: stind.i4 // store the 5 into the address
IL_0004: ret
} // end of method BasicTesting::SetMyFriend
No surprises - x is passed in as an int32& or a reference to a four byte int - ah, but it has the magic [out] attribute on it as well.
Here is TouchMyFriend():
.method private hidebysig instance void TouchMyFriend(int32& x) cil managed
{
// Code size 5 (0x5)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.1
IL_0002: ldc.i4.3
IL_0003: stind.i4
IL_0004: ret
} // end of method BasicTesting::TouchMyFriend
You'll notice that except for the [out] in the declaration, it is absolutely identical to SetMyFriend. I won't insert the calling code, but it is precisely the same for both. So the answer to the question is that the compiler enforces it to the degree that it will attempt to determine if the value has been assigned via static analysis. Yet, is this 100% the case because the parameter is marked as [out]. Does the CLR do the verification? The answer is no - if you write the following managed C++ method:
static void DontTouchMe([System::Runtime::InteropServices::Out]int __gc &x)
{
}
the C++ compiler lets it go by and at runtime, the CLR lets it work too. This is moderately distressing, but not surprising. Lesson: out is only fully honored in C#.