Consider the following code (I have purposefully written MyPoint to be a reference type for this example)
public class MyPoint
{
public int x;
public int y;
}
It is universally acknowledged (in C# at least) that when you pass by reference, the method contains a reference to the object being manipulated, whereas when you pass by value, the method copies the value being manipulated, thus the value in global scope is not affected.
Example:
void Replace<T>(T a, T b)
{
a = b;
}
int a = 1;
int b = 2;
Replace<int>(a, b);
// a and b remain unaffected in global scope since a and b are value types.
Here is my problem; MyPoint
is a reference type, thus I would expect the same operation on Point
to replace a
with b
in global scope.
Example:
MyPoint a = new MyPoint { x = 1, y = 2 };
MyPoint b = new MyPoint { x = 3, y = 4 };
Replace<MyPoint>(a, b);
// a and b remain unaffected in global scope since a and b...ummm!?
I expected a
and b
to point to the same reference in memory...can someone please clarify where I have gone wrong?
Re: OP's Assertion
It is universally acknowledged (in C# at least) that when you pass by reference, the method contains a reference to the object being manipulated, whereas when you pass by value, the method copies the value being manipulated ...
TL;DR
There's more to it than that. Unless you pass variables with the ref or out keywords, C# passes variables to methods by value, irrespective of whether the variable is a value type or a reference type.
If passed by reference, then the called function may change the variable's address at the call-site (i.e. change the original calling function's variable's assignment).
If a variable is passed by value:
Since this is all rather complicated, I would recommend avoiding passing by reference if possible (instead, if you need to return multiple values from a function, use a composite class, struct, or Tuples as a return
type instead of using the ref
or out
keywords on parameters)
Also, when passing reference types around, a lot of bugs can be avoided by not changing (mutating) fields and properties of an object passed into a method (for example, use C#'s immutable properties to prevent changes to properties, and strive to assign properties only once, during construction).
In Detail
The problem is that there are two distinct concepts:
Unless you explicitly pass (any) variable by reference, by using the out
or ref
keywords, parameters are passed by value in C#, irrespective of whether the variable is a value type or reference type.
When passing value types (such as int
, float
or structs like DateTime
) by value (i.e. without out
or ref
), the called function gets a copy of the entire value type (via the stack).
Any change to the value type, and any changes to any properties / fields of the copy will be lost when the called function is exited.
However, when passing reference types (e.g. custom classes like your MyPoint
class) by value
, it is the reference
to the same, shared object instance which is copied and passed on the stack.
This means that:
x
or y
are seen by anyone observing the object)What happens here:
void Replace<T>(T a, T b) // Both a and b are passed by value
{
a = b; // reassignment is localized to method `Replace`
}
for reference types T
, means that the local variable (stack) reference to the object a
is reassigned to the local stack reference b
. This reassign is local to this function only - as soon as scope leaves this function, the re-assignment is lost.
If you really want to replace the caller's references, you'll need to change the signature like so:
void Replace<T>(ref T a, T b) // a is passed by reference
{
a = b; // a is reassigned, and is also visible to the calling function
}
This changes the call to call by reference - in effect we are passing the address of the caller's variable to the function, which then allows the called method to alter the calling method's variable.
However, nowadays:
Tuple
or a custom class
or struct
which contains all such return variables.Edit
These two diagrams may help with the explanation.
Pass by value (reference types):
In your first instance (Replace<T>(T a,T b)
), a
and b
are passed by value. For reference types, this means the references are copied onto the stack and passed to the called function.
main
) allocates two MyPoint
objects on the managed heap (I've called these point1
and point2
), and then assigns two local variable references a
and b
, to reference the points, respectively (the light blue arrows):MyPoint a = new MyPoint { x = 1, y = 2 }; // point1
MyPoint b = new MyPoint { x = 3, y = 4 }; // point2
The call to Replace<Point>(a, b)
then pushes a copy of the two references onto the stack (the red arrows). Method Replace
sees these as the two parameters also named a
and b
, which still point to point1
and point2
, respectively (the orange arrows).
The assignment, a = b;
then changes the Replace
methods' a
local variable such that a
now points to the same object as referenced by b
(i.e. point2
). However, note that this change is only to Replace's local (stack) variables, and this change will only affect subsequent code in Replace
(the dark blue line). It does NOT affect the calling function's variable references in any way, NOR does this change the point1
and point2
objects on the heap at all.
Pass by reference:
If however we we change the call to Replace<T>(ref T a, T b)
and then change main
to pass a
by reference, i.e. Replace(ref a, b)
:
As before, two point objects allocated on the heap.
Now, when Replace(ref a, b)
is called, while main
s reference b
(pointing to point2
) is still copied during the call, a
is now passed by reference, meaning that the "address" to main's a
variable is passed to Replace
.
Now when the assignment a = b
is made ...
It is the the calling function, main
's a
variable reference which is now updated to reference point2
. The change made by the re-assignment to a
is now seen by both main
and Replace
. There are now no references to point1
Changes to (heap allocated) object instances are seen by all code referencing the object
In both scenarios above, no changes were actually made to the heap objects, point1
and point2
, it was only local variable references which were passed and re-assigned.
However, if any changes were actually made to the heap objects point1
and point2
, then all variable references to these objects would see these changes.
So, for example:
void main()
{
MyPoint a = new MyPoint { x = 1, y = 2 }; // point1
MyPoint b = new MyPoint { x = 3, y = 4 }; // point2
// Passed by value, but the properties x and y are being changed
DoSomething(a, b);
// a and b have been changed!
Assert.AreEqual(53, a.x);
Assert.AreEqual(21, b.y);
}
public void DoSomething(MyPoint a, MyPoint b)
{
a.x = 53;
b.y = 21;
}
Now, when execution returns to main
, all references to point1
and point2
, including main's
variables a
and b
, which will now 'see' the changes when they next read the values for x
and y
of the points. You will also note that the variables a
and b
were still passed by value to DoSomething
.
Changes to value types affect the local copy only
Value types (primitives like System.Int32
, System.Double
) and structs (like System.DateTime
, or your own structs) are allocated on the stack, not the heap, and are copied verbatim onto the stack when passed into a call. This leads to a major difference in behaviour, since changes made by the called function to a value type field or property will only be observed locally by the called function, because it only will be mutating the local copy of the value type.
e.g. Consider the following code with an instance of the mutable struct, System.Drawing.Rectangle
public void SomeFunc(System.Drawing.Rectangle aRectangle)
{
// Only the local SomeFunc copy of aRectangle is changed:
aRectangle.X = 99;
// Passes - the changes last for the scope of the copied variable
Assert.AreEqual(99, aRectangle.X);
} // The copy aRectangle will be lost when the stack is popped.
// Which when called:
var myRectangle = new System.Drawing.Rectangle(10, 10, 20, 20);
// A copy of `myRectangle` is passed on the stack
SomeFunc(myRectangle);
// Test passes - the caller's struct has NOT been modified
Assert.AreEqual(10, myRectangle.X);
The above can be quite confusing and highlights why it is good practice to create your own custom structs as immutable.
The ref
keyword works similarly to allow value type variables to be passed by reference, viz that the 'address' of the caller's value type variable is passed onto the stack, and assignment of the caller's assigned variable is now directly possible.