I experienced that the terms value type, reference type, pass by value, pass by reference are sometimes confusing for the beginning C# programmer (I include myself to this category).
Therefore I will try to explain this as clearly as possible with some small examples.

Value type versus reference type

A typical example of a value type is the int type. Setting a value type to null is not allowed (unless you make it nullable).

Value Type

  1. int myNumber = 5;
  2. //following line results in an error!
  3. myNumber = null;

An example of a reference type is the StringBuilder type.

Reference Type

  1. StringBuilder myString = new StringBuilder();
  2. //a reference type can be set to null
  3. StringBuilder yourString = null;

If we show this in a diagram we have:

Value type versus reference type

myNumber is value type. myString and yourString are reference type.

Because myNumber is a value type it holds the value 5. myString is a reference type so it doesn’t contain the created StringBuilder object itself, but refers to it by holding the in memory address of the object. For simplicity I called this “address1”. yourString does not refer to any object because it is set to null.

Let’s have an example.

  1.  int x = 5;
  2.  int y = x;
  3.  y++;
  4.  Console.WriteLine("x is {0}, y is {1}", x,y);
  5.            
  6.  StringBuilder myString = new StringBuilder("Hello");
  7.  StringBuilder yourString = myString;
  8.  yourString.Append(" world!");
  9.  Console.WriteLine("myString is {0}, yourString is {1}",
  10.                    myString, yourString);
the console output
  1. x is 5, y is 6
  2. myString is Hello world!, yourString is Hello world!

The value types x and y are independent from each other. The reference types myString and yourString point to the same object.

Pass by value, pass by reference

When calling a function with parameters, the arguments passed to the function are passed by value.

pass a value type by value, makes a copy
  1. static void Main()
  2.   {
  3.     int x = 5;
  4.     InCrement(x);
  5.     Console.WriteLine(x);
  6.   }
  7.  
  8. static void InCrement (int yourInt)
  9.   {yourInt++;}
the console output
  1. your value is 5

A copy of x is passed to the function, where it lives its own live.
If we want to work on the original value, we have to pass this value by referring to it.
We do this by adding the keyword ref before the corresponding parameter in the function and the argument of the function we call.

pass a value type by reference
  1. static void Main()
  2. {
  3.   int x = 5;
  4.   InCrement(ref x);
  5.   Console.WriteLine("your value is {0}", x);
  6. }
  7.  
  8. static void InCrement (ref int yourInt)
  9. {yourInt++;}
the console output
  1. your value is 6

Passing a reference type doesn’t always mean you work on the original value

Look at the following 2 examples, where we want to swap two objects.
The first example passes the reference types by value, the second one passes them by reference.

passing reference types by value
  1. static void Main()
  2. {
  3.   StringBuilder first = new StringBuilder("first");
  4.   StringBuilder second = new StringBuilder("second");
  5.   swapStringBuilder(first, second);
  6.   Console.WriteLine("first is {0}, second is {1}",
  7.                 first, second);
  8. }
  9.  
  10. static void swapStringBuilder(StringBuilder first,
  11.                              StringBuilder second)
  12. {
  13.   StringBuilder temp = first;
  14.   first = second;
  15.   second = temp;
  16. }
the console output
  1. first is first, second is second

Why aren’t the objects swapped? Because we pass the addresses of the objects by value. In the swap function new reference types are created (passing by value makes copies!) and initialized with the addresses of the objects we pass to the function. Further nothing is done within the function.

passing reference types by reference
  1. static void Main()
  2. {
  3.   StringBuilder first = new StringBuilder("first");
  4.   StringBuilder second = new StringBuilder("second");
  5.   swapStringBuilder(ref first, ref second);
  6.   Console.WriteLine("first is {0}, second is {1}",
  7.                 first, second);
  8. }
  9.  
  10. static void swapStringBuilder(ref StringBuilder first,
  11.               ref StringBuilder second)
  12. {
  13.   StringBuilder temp = first;
  14.   first = second;
  15.   second = temp;
  16. }
the console output
  1. first is second, second is first

Here we work on the original references, only one new reference temp was made.

Final example

Now you should also understand the last example.

passing reference type by value
  1. static void Main()
  2. {
  3.   StringBuilder s = new StringBuilder("hello");
  4.   addSome(s);
  5.   Console.WriteLine(s);
  6. }
  7.  
  8. static void addSome(StringBuilder stringBuilder)
  9. {
  10.   stringBuilder.Append(" world!");
  11. }
the console output
  1. hello world!

We pass the reference type by value, so a new reference is made in the function, but that reference holds the same address of the original reference. So if we add some text, the text is added to the original object referenced by s.

I hope this helps to better understand reference versus value.

Rating 4.17 out of 5
[?]