The primary reason youād use ārefā is to allow the method youāve called the ability to modify what youāve passed in. In the case of structs/value types, it means we can update the struct/value and the calling code has its member updated as well. In the case of a reference type (class), it means the variable will be updated.
Give you an example of a use case for ārefā:
Array.Resize
Array.Resize takes in a reference type (an array is a class, and therefore a reference type). The reason it takes it in by ārefā is because itās not actually resizing an array. Itās creating a new array of the size in question, copying the contents of the passed in array to it, and making sure that the variable you passed it in as is updated to this new reference.
This is an example of how ārefā to a reference type is beneficial.
ā¦
For a value/struct type you could see an example like Interlocked.Increment:
This increments an int/long as an atomic operation. Itās done by ārefā so that the variable you want incremented is done so all self contained in the method as an atomic operation. If it had returned a value the āsettingā of the variable wouldnāt be part of the atomic operation and thusly could come out of sync in a threaded situation.
To see what I meanā¦ when you say any of these:
i++;
i += 1;
i = i + 1;
(note the first 2 are really just syntax sugar for the last one)
What the program actually sees this as, is:
copy i into operating registry r1
copy 1 into operating registry r2
sum r1 and r2 placing result in r1
set i to r1
In IL you can see this:
IL_0003: ldloc.0 // i
IL_0004: ldc.i4.1
IL_0005: add
IL_0006: stloc.0 // i
(note again, all 3 ways of writing that compile to the same IL)
In a threaded situation if you did i++ simultaneouslyā¦ both could interweave at any point in that statement chain.
If you have method like so:
int Increment(int value)
{
return value + 1;
}
i = Increment(i);
What the program actually sees is:
allocate stack frame for function 'Increment'
copy the value of i onto call stack for 'value'
copy value into operating registry r1
copy 1 into operating registry r2
sum r1 and r2 placing result in r1
drop stack frame
place result at position of stack frame
set i to result
Thereās even more odds of interweaving here.
By adding in the ārefā, weāre no longer copying in the value of i, we instead reference the location of i in memory. Now that Interlocked.Increment has that location/address it can create a lock on that addressā¦ while itās locked no other lock can be established until it is released. So we result in:
void Increment(ref int value) ...
Increment(ref i);
Is:
allocate stack frame for function 'Increment'
copy the address of i onto call stack for 'value' ('value' and 'i' both point at the same address)
lock the address of 'value'
copy value into operating registry r1
copy 1 into operating registry r2
sum r1 and r2 placing result in r1
set value to r1 (and thusly setting i to r1 as well since they're both the same address)
release lock
drop stack frame
By doing this, we ensure that i isnāt volatile for the duration of the operation. Itās all atomic.
If we had returned the result, the operation would become volatile, and we couldnāt control the order at which i is set.