28

I have made some ref keyword tests and there is one thing I can't understand:

static void Test(ref int a, ref int b)
{
    Console.WriteLine(Int32.ReferenceEquals(a,b));
}

static void Main(string[] args)
{
    int a = 4;
    Test(ref a, ref a);
    Console.ReadLine();
}

Why does this code display False? I know that int is a value type but here it should pass references to the same object.

Youssef13
  • 3,836
  • 3
  • 24
  • 41
amergan
  • 297
  • 2
  • 4
  • because the references aren't similar for value types. – Amit Kumar Ghosh Jul 07 '15 at 11:15
  • 1
    The `ref` modifier does not cause the corresponding argument to be boxed to a reference type. – Lee Jul 07 '15 at 11:15
  • Are you trying to see if the two parameters are references to the same variable? – BoltClock Jul 07 '15 at 11:17
  • @BoltClock: If that was the intention, it can be done with some 'hard' work ;p – leppie Jul 07 '15 at 12:05
  • This is not going to be useful since basic value types can only be changed by assigning a new value (so value equality is all you need). [This](https://msdn.microsoft.com/en-us/library/s1ax56ch.aspx) short document explains how value types work. [This](https://msdn.microsoft.com/en-us/library/4d43ts61.aspx) is also good. – Trisped Jul 07 '15 at 22:08

4 Answers4

41

Why does this code display False?

Because int a and int b are being boxed when you call object.ReferenceEquals. Each integer is boxed inside an object instance. Thus, you are actually comparing references between two boxed values, which clearly aren't equal.

You can easily see this if you look at the generated CIL for the method:

Test:
IL_0000:  nop
IL_0001:  ldarg.0     Load argument a
IL_0002:  ldind.i4
IL_0003:  box         System.Int32
IL_0008:  ldarg.1     Load argument b
IL_0009:  ldind.i4
IL_000A:  box         System.Int32
IL_000F:  call        System.Object.ReferenceEquals
IL_0014:  call        System.Console.WriteLine
IL_0019:  nop
IL_001A:  ret

Checking for storage location equality can be achieved either by using verifiable CIL (such as in @leppie's answer) or by unsafe code:

unsafe static void Main(string[] args)
{
    int a = 4;
    int b = 5;
    Console.WriteLine(Test(ref a, ref a)); // True
    Console.WriteLine(Test(ref a, ref b)); // False;
}

unsafe static bool Test(ref int a, ref int b)
{
    fixed (int* refA = &a)
    fixed (int* refB = &b)
    {
        return refA == refB;
    }
}
Community
  • 1
  • 1
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • 1
    What happens if you change one of them inside method? i think this is not possible – M.kazem Akhgary Jul 07 '15 at 11:15
  • @M.kazemAkhgary I didn't understand your question. – Yuval Itzchakov Jul 07 '15 at 11:15
  • What happens if you change `a` or `b` inside `Test`. I mean they are both referencing same thing (`a` in Main). i mean if you change one of them the other one will change too? – M.kazem Akhgary Jul 07 '15 at 11:17
  • 2
    @M.kazemAkhgary Yes, if you directly manipulate `a` it will change it's value. This (the behavior the OP is seeing) happens because when calling `ReferenceEquals`, both values get boxed (just for the method call). – Yuval Itzchakov Jul 07 '15 at 11:19
  • Hmm, I wonder if `ldarg.0; ldarg.1; ceq;` can be emitted in C#? – leppie Jul 07 '15 at 11:30
  • 1
    @YuvalItzchakov: you can get that going unsafe: ie `static bool Foo(int* a, int* b) => a == b;` But can you do it without unsafe? See: http://tryroslyn.azurewebsites.net/#f:>ilr/K4Zwlgdg5gBAygTxAFwKYFsDcAoFBDZMAYxiIBs8QQYBhGAb2wEhgIQ8AzVGfQkgIwD2gsjAAqqFAApIyAFQw8AGhgxZC/gEoYAXgB8i3Tpj8cAXyAA= – leppie Jul 07 '15 at 11:35
  • 1
    @YuvalItzchakov: hence why you need `==`. You can do it in IL, and it is verifiable, but cant see a way in C#. – leppie Jul 07 '15 at 11:48
19

This cannot be done directly in C#.

You can however implement it in verifiable CIL:

.method public hidebysig static bool Test<T>(!!T& a, !!T& b) cil managed
{
  .maxstack 8
  ldarg.0 
  ldarg.1 
  ceq 
  ret 
}

Tests

int a = 4, b = 4, c = 5;
int* aa = &a; // unsafe needed for this
object o = a, p = o;
Console.WriteLine(Test(ref a, ref a)); // True
Console.WriteLine(Test(ref o, ref o)); // True
Console.WriteLine(Test(ref o, ref p)); // False
Console.WriteLine(Test(ref a, ref b)); // False
Console.WriteLine(Test(ref a, ref c)); // False
Console.WriteLine(Test(ref a, ref *aa)); // True
// all of the above works for fields, parameters and locals

Notes

This does not actually check for the same reference, but even more fine-grained in that it makes sure both are the same 'location' (or referenced from the same variable) too. This is while the 3rd line returns false even though o == p returns true. The usefulness of this 'location' test is very limited though.

leppie
  • 115,091
  • 17
  • 196
  • 297
  • Note: this is not meant as the answer, just a roundabout way of achieving it. – leppie Jul 07 '15 at 12:28
  • 1
    Outstanding. I created a `C#` answer for this using `DynamicMethod` [here](http://stackoverflow.com/a/43570334/147511). Many thanks. – Glenn Slayden Apr 23 '17 at 11:07
2

I know, that int is a value type but here it should pass references to the same object.

Yes, the reference passed to the method are the same, but they are boxed (converted to object/reference type) in the ReferenceEquals method.

That is why the result of your test returns false, since you are comparing references of two different objects, due to boxing.

See: Object.ReferenceEquals Method

When comparing value types. If objA and objB are value types, they are boxed before they are passed to the ReferenceEquals method. This means that if both objA and objB represent the same instance of a value type, the ReferenceEquals method nevertheless returns false

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Habib
  • 219,104
  • 29
  • 407
  • 436
1

The confusion here is because unlike pointers (as in * ), "ref" in C# is not a part of a type, but a part of a method signature. It applies to the parameter and means "this must not be copied". It does not mean "this argument has reference type".

Parameter passed by ref, instead of representing a new storage location, is instead an alias to some existing location. How alias is created is technically an implementation detail. Most often aliases are implemented as managed references, but not always. In some async related cases, for example, a reference to an array element could be internally represented as a combination of array and index.

Essentially for all purposes your a and b are still understood by C# as int-typed variables. It is legal and completely normal to use them in any expression that takes int values like a+b, or SomeMethod(a,b) and in those cases the actual int values stored in a and b are used.

There is really no concept of a "reference" as an entity that you can directly work with in C#. Unlike pointers, the actual values of managed references must be assumed to be able to change at any moment, or even asynchronously, by GC, so the set of meaningful scenarios on managed references would be extremely limited.

VSadov
  • 961
  • 8
  • 4