C# how dereferencing works behind the scenes

130 Views Asked by At

This code is from book named "Pro C# with .NET 6" and I wonder what happens behind the scenes while the reference to reference(ref Person p) is passed to the method. for example when we do p.personAge = 555; does dereferencing happen twice behind the scenes to first get to the variable which is being pointed to by p and then to get to value which the variable pointed by p contains? sorry if question is bit confusing but overall I want to know how dereferencing works in C#, in this case do we have 2 way dereferencing which happens automatically by the compiler?

static void SendAPersonByReference(ref Person p)
{
 // Change some data of "p".
 p.personAge = 555;
...
}

does dereferencing happens in this code too?

static void SendAPersonByReference(Person p)
{
 // Change some data of "p".
 p.personAge = 555;
...
}
2

There are 2 best solutions below

10
Marc Gravell On BEST ANSWER

Yes, there is a double-dereference; this is encoded in the IL; see here, with the key portion being:

(by-reference)

    IL_0000: ldarg.0 # load the value of p, a ref-ref-Person (push 1)
    IL_0001: ldind.ref # dereference the ref-ref to a ref-Person (pop 1, push 1)
    IL_0002: ldc.i4 555 # load the constant integer 555 (push 1)
    IL_0007: stfld int32 Person::personAge # store to field (pop 2)
    IL_000c: ret

vs (by value)

    IL_0000: ldarg.0 # load the value of p, a ref-Person (push 1)
    IL_0001: ldc.i4 555 # load the constant integer 555 (push 1)
    IL_0006: stfld int32 Person::personAge # store to field (pop 2)
    IL_000b: ret

The ldind.ref is the extra dereference, from a ref Person (a reference to a reference) to a Person (a reference).

5
Joel Coehoorn On

ref only comes into play when you assign to or alter the variable itself, and the p.personAge = 555; statement doesn't do that in either sample. Yes, an assignment does happen here, but not until after you first retrieve the object and then look up the personAge property on that object, which happens without needing ref.

A better example to understand the difference looks like this, assuming Person is a reference type (class) rather than value type (struct):

static void SendAPerson(Person p)
{
    p = new Person();
    p.personAge = 555;
}

static void SendAPersonByReference(ref Person p)
{
    p = new Person();
    p.personAge = 555;
}

static void SendAPerson2(Person p)
{
    p.personAge = 555;
}

With these new samples we need to think in terms of what happens in code that calls these methods, rather than in the methods themselves.

With the first (normal) method, nothing changes with the original Person variable used to call this method. The new object created in the method becomes eligible for garage collection as soon as the method ends, and the 555 value set in the method is lost.

With the second (reference) method, we replaced the contents of the original variable. It now holds our new object, with the 555 value in the personAge property. Any object that was there is now eligible for garbage collection (assuming no other extant references).

With the third (also normal) method, we change the personAge property on the provided object, rather than creating a new object, and this change is seen in the original object after the method completes.