Re: OP Assertion
It is generally accepted (at least in C #) that when you pass by reference, the method contains a reference to the manipulated object, while when passing by value, the method copies the manipulated value ...
TL DR
If you don't pass variables with the ref or out keywords, C # passes the variables to methods by value , regardless of whether the variable is a value type or a reference type.
When passed by reference, the called function can change the address of the variable (i.e., change the initial assignment of the variable to the calling function).
If the variable is passed by value:
- if the called function reassigns the variable, this change is local only to the called function and will not affect the original variable in the calling function
- however, if changes are made to field variables or properties by the called function, it will depend on whether the variable is a value type or a reference type to determine whether the calling function will see the changes made to that variable.,
Since this is all quite complicated, I would recommend avoiding passing by reference if possible (use a compound class or structure as the return type instead, or use Tuples)
In addition, when passing reference types, you can avoid many errors by not changing (changing) the fields and properties of the object passed to the method (for example, use immutable C # properties to prevent changes to properties once assigned during construction).
In details
The problem is that there are two different concepts:
- Value types (e.g. int) and reference types (e.g. string or custom classes)
- Pass by value (default behavior) versus pass by reference (ref, out)
Unless you explicitly pass (any) a variable by reference using the out or ref keywords, the parameters are passed by value in C # regardless of whether the variable is a value type or a reference type.
When passing value types (such as int , float or structures of type DateTime ), the called function receives a copy of the entire value type (via the stack).
Any change in the value type and any changes in any properties / fields of the copy will be lost upon exiting the called function.
However, in the case of reference types (for example, custom classes such as your MyPoint class), this is a reference to the same instance of a shared object that is copied and MyPoint onto the stack.
It means that:
- Any changes to the fields or properties of a shared object are permanent (i.e., any changes to
x or y ) - However, the link itself is still copied (passed by value), so any change to the copy of the link will be lost. This is why your code is not working properly
What's going on 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 link to the local variable (stack) for the object a reassigned to the link to the local stack b . This reassignment is local only to this function - as soon as the scope leaves this function, the reassignment is lost.
If you really want to replace the links of callers, you need to change the signature as follows:
void Replace<T>(ref T a, T b)
This changes the call to the call by reference - in fact, we pass the address of the variable of the calling function to a function, which then allows the called method to change the variable of the calling method.
However, at present:
- Passing by reference is generally considered a bad idea - instead, we must either pass the returned data to the return value, and if more than one variable needs to be returned, then use a
Tuple or a custom class or struct that contains all such returned variables - Changing ("mutating") a shared variable (or even a reference) in the called method is not approved, especially by the functional programming community, as this can lead to complex errors, especially when using multiple threads. Instead, give preference to immutable variables, or if mutation is required, consider changing the (potentially deep) copy of the variable. You can find topics related to "pure functions" and "correct constant" for further reading.
edit
These two charts can help with the explanation.
Pass by value (reference types):
In your first case ( Replace<T>(T a,T b) ) a and b are passed by value. For reference types, this means that the links are copied onto the stack and passed to the called function.

- Your source code (I called it
main ) selects two MyPoint in the managed heap (I named these point1 and point2 ), and then assigns two links a local variables a and b to refer to points respectively (blue arrows indicator):
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 links on the stack (red arrows). The Replace method considers them as two parameters, also called a and b , which still point to point1 and point2 respectively (orange arrows).
Appointment, a = b; then modifies the Replace methods a local variable in such a way that it now points to the same object as a b refers (i.e. point2 ). However, note that this change is intended only to replace local (stack) variables, and this change will only affect subsequent code in the Replace (blue line). This does not affect the variable references of the calling function, NOR is point1 point2 objects point1 and point2 on the heap.
Pass by reference:
However, if 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 are allocated in the heap.
Now, when Replace(ref a, b) is called, while main link b (pointing to point2 ) is still copied during the call, it is now passed by reference, which means that the "address" to the main a a variable is passed Replace
Now that the assignment a = b done ...
This is a call function, the main reference to a variable that is currently being updated to refer to point2 . Changes made when reassigning a are now visible as main and Replace . There are no references to point1
Changes (allocated on the heap) of object instances are visible with all the code referencing the object
In both of the above scenarios, virtually no changes were made to the heap objects, point1 and point2 , these were only references to local variables that were transferred and reassigned.
However, if any changes were actually made to the heap objects point1 and point2 , then all variable references to these objects will see these changes.
For example:
void main() { MyPoint a = new MyPoint { x = 1, y = 2 };
Now, when the execution returns to main , all references to point1 and point2 , including the variables main's a and b , will now βseeβ the changes the next time they read the values ββfor x and y points. You will also notice that variables a and b still passed by value to DoSomething .
Changes in value types only affect the local copy
Value types (primitives such as System.Int32 , System.Double and structures like System.DateTime ) are placed on the stack, not on the heap, and are literally copied onto the stack when passed to the call. One of the differences is that changes made by the called function to the field or property of the value type will be observed only by the locally called function, since it will only change the local copy of the value type.
For example, consider the following code with an instance of the System.Drawing.Rectangle mutable structure
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 struct has NOT been modified Assert.AreEqual(10, myRectangle.X);
The above can be quite confusing and emphasizes why it is recommended that you create your own custom structures as immutable.
The ref keyword works in a similar way, allowing you to pass variables of type value by reference, namely, that the "address" of a variable like the value of the caller is pushed onto the stack, and now assigning the variable assigned by the caller is directly possible.