By series0ne


2015-04-13 13:25:47 8 Comments

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?

7 comments

@StuartLC 2015-04-13 13:30:12

Re:

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

Unless you pass variables with the ref or out keywords, C# passes variables to methods by value.

In Detail

The problem is that there are two distinct concepts:

  • Value Types (e.g. int) vs Reference Types (e.g. string)
  • Passing by Value (default behaviour) vs Passing by Reference(ref, out)

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), 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, with reference types (like string, and custom classes like your MyPoint class), it is the reference to the same, shared object instance which is copied and passed on the stack.

This means that:

  • Any changes to the fields or properties of the shared object are permanent (i.e. any changes to x or y)
  • However, the reference itself is still copied (passed by value), so any change to the copy of the reference will be lost. This is why your code doesn't work as expected

What happens here:

void Replace<T>(T a, T b)
{
    a = b;
}

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 = b;
}

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:

  • Passing by reference is generally regarded as a bad idea - instead, we should either pass return data in the return value, and if there is more than one variable to be returned, then use a Tuple or a custom class or struct which contains all such return variables.
  • Changing ('mutating') a shared value (and even reference) variable in a called method is frowned upon, especially by the Functional Programming community, as this can lead to tricky bugs, especially when using multiple threads. Instead, give preference to immutable variables, or if mutation is required, then consider changing a (potentially deep) copy of the variable. You might find topics around 'pure functions' and 'const correctness' interesting further reading.

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.

enter image description here

  1. Your initial code (I've called this 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
  1. 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).

  2. 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):

enter image description here

  1. As before, two point objects allocated on the heap.

  2. Now, when Replace(ref a, b) is called, while mains 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.

  3. Now when the assignment a = b is made ...

  4. 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) are allocated on the stack, not the heap, and are copied verbatim onto the stack when passed into a call. One difference here is that 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.

@RayLoveless 2018-06-22 21:10:50

By default c# passes ALL arguements by value... that is why a and b remain unaffected in global scope in your examples. Here's a reference for those down voters.

@series0ne 2018-06-22 21:12:33

I think the confusion for a lot of beginners is that even references are passed by value

@RayLoveless 2018-07-10 18:06:13

@series0ne . Agreed... who is down voting this?

@Kevin DiTraglia 2015-04-13 13:29:13

C# is actually pass by value. You get the illusion it's pass by reference, because when you pass a reference type you get a copy of the reference (the reference was passed by value). However, since your replace method is replacing that reference copy with another reference, it's effectively doing nothing (The copied reference goes out of scope immediately). You can actually pass by reference by adding the ref keyword:

void Replace<T>(ref T a, T b)
{
    a = b;
}

This will get you your desired result, but in practice is a little strange.

@Omri Aharon 2015-04-13 13:29:43

C# is passing reference types objects not by reference, but rather it's passing the reference by value. Meaning you can mess around with their insides, but you can't change the assignment itself.

Read this great piece by Jon Skeet for deeper understanding.

@Amir Popovich 2015-04-13 13:34:04

In C# all the params that you pass to a method are passed by value.
Now before you shout keep on reading:

A value-type's value is the data that is copied while a reference type's value is actually a reference.

So when you pass an objects reference to a method and change that object then the changes will reflect outside the method as well since you are manipulating the same memory the object was allocated.

public void Func(Point p){p.x = 4;}
Point p = new Point {x=3,y=4};
Func(p);
// p.x = 4, p.y = 4

Now Lets look at this method:

public void Func2(Point p){
 p = new Point{x=5,y=5};
}
Func2(p);
// p.x = 4, p.y = 4

So no changed occurred here and why? Your method simply created a new Point and changed p's reference(Which was passed by value) and therefore the change was local. You didn't manipulate the point, you changed the reference and you did locally.

And there comes the ref keyword that saves the day:

public void Func3(ref Point p){
 p = new Point{x=5,y=5};
}
Func3(ref p);
// p.x = 5, p.y = 5

The same occurred in your example. You assigned a point with a new reference, but you did it locally.

@libik 2015-04-13 13:30:11

You dont get it right.

It is similar like Java - everything is passed by value! But you do have to know, what the value is.

In primitive data types, the value is the number itself. In other cases it is reference.

BUT, if you copy reference to another variable, it holds same reference, but does not reference the variable (thus it is not pass by reference known in C++).

@Russ 2015-04-13 13:29:44

You're not understanding what passing by reference means. Your Replace method is creating a copy of the Point object--passing by value (which is actually the better way of doing it).

To pass by reference, so that a and b both reference the same point in memory, you need add "ref" to the signature.

@Omri Aharon 2015-04-13 14:02:57

The object itself is not copied, but the reference to it is. Thus, if you change anything within that class, the change will persist when you exit the function.

Related Questions

Sponsored Content

21 Answered Questions

[SOLVED] Cast int to enum in C#

  • 2008-08-27 03:58:21
  • lomaxx
  • 1072149 View
  • 2669 Score
  • 21 Answer
  • Tags:   c# enums casting

17 Answered Questions

77 Answered Questions

[SOLVED] Is Java "pass-by-reference" or "pass-by-value"?

9 Answered Questions

[SOLVED] What are the correct version numbers for C#?

56 Answered Questions

[SOLVED] What is the difference between String and string in C#?

29 Answered Questions

[SOLVED] Is JavaScript a pass-by-reference or pass-by-value language?

26 Answered Questions

[SOLVED] How do I enumerate an enum in C#?

25 Answered Questions

[SOLVED] Get int value from enum in C#

  • 2009-06-03 06:46:39
  • jim
  • 1170261 View
  • 1446 Score
  • 25 Answer
  • Tags:   c# enums casting int

12 Answered Questions

[SOLVED] Why should I use the keyword "final" on a method parameter in Java?

23 Answered Questions

[SOLVED] How do I pass a variable by reference?

Sponsored Content