Value Types vs. Reference Types

One area of C# that causes confusion is the difference between value and reference types.

Reference Types

 

Let’s talk about reference types for a moment. A common reference type used in .NET is StringBuilder (others include Form, String, All Arrays, Classes, and Delegates). Take a look at this example, do you know what the output will be?

   1: private void Main_Load(object sender, EventArgs e)
   2: {
   3:
   4:     StringBuilder X = new StringBuilder("It's Raining In ");
   5:     AppendCityName(ref X);
   6:     Console.WriteLine(X.ToString());
   7: }
   8:
   9: private void AppendCityName(ref StringBuilder Source) {
  10:
  11:     Source.Append("Seattle");
  12:
  13: }

 

The correct answer is “It’s Raining In Seattle”. We pass our new StringBuilder into AppendCityName explicitly by reference and AppendCityName acts on the reference to the original StringBuilder. Nothing too surprising here.

 

Now try this one (I’ve taken away the “ref” keyword – resulting in X being passed By Value), what will the output be?

 

   1: private void Main_Load(object sender, EventArgs e)
   2: {
   3:
   4:     StringBuilder X = new StringBuilder("It's Raining In ");
   5:     AppendCityName(X);
   6:     Console.WriteLine(X.ToString());
   7: }
   8:
   9: private void AppendCityName(StringBuilder Source) {
  10:
  11:     Source.Append("Seattle");
  12:
  13: }

 

Did you guess “It’s Raining In Seattle”? The output is identical to the first example.

 

What’s going on here?

Did you expect the output to be “It’s Raining In ”?

StringBuilder is a reference type. That is to say, it lives on the heap (instead of the stack), and the behind-the-scenes value of X in the example above is just the address to the new StringBuilder object created on the heap.

In both examples, only the address to the original StringBuilder on the heap is being passed into the method, and even in the By Value (Second) Example, the StringBuilder data is NOT being copied or moved around at all.

There is a difference between the two, however. In the first example, both X in Main_Load() and Source in AppendCityName() point directly to the same address on the heap. In the second example, Source points to X in Main_Load(), which points to the StringBuilder on the heap.

 

Why is this important?

Passing a reference type by value or by reference is functionally the same 90% of the time. The problem is the remaining 10% of the time. Take the following example:

 

   1: private void Main_Load(object sender, EventArgs e)
   2: {
   3:
   4:     StringBuilder X = new StringBuilder("It's Raining In ");
   5:     AppendCityName(ref X);
   6:     Console.WriteLine(X.ToString());
   7: }
   8:
   9: private void AppendCityName(ref StringBuilder Source) {
  10:
  11:     Source.Append("Seattle");
  12:     Source = null;
  13:
  14: }

…which, if you were to execute, unfortunately results in…

 

Ooops. Because the StringBuilder is passed by reference, AppendCityName() is working with the original reference to the object, and not a copy of the reference. If we were passing the StringBuilder by value, this would have never happened. When in doubt, pass reference types by value and not by reference!

 

Value Types

 

Value types are the complimenting type to reference types. Instead of being stored on the heap, value types are stored on the stack (unless boxed). Integers, Booleans, and Structs are common examples of value types.

When value types are passed By Value, the actual value is copied and there are no references involved. When value types are passed By Reference, the method argument is initialized with the address pointing back to the original value in the calling routine.

 

Here’s an example of passing a value type by reference:

 

   1: private void Main_Load(object sender, EventArgs e)
   2: {
   3:
   4:     int TotalDragonCount = 1;
   5:     IncrementDragons(ref TotalDragonCount);
   6:     Console.Write(TotalDragonCount);
   7:
   8: }
   9:
  10: private void IncrementDragons(ref int OriginalDragonCount) {
  11:
  12:     OriginalDragonCount++;
  13:
  14: }

 

The output is 2. TotalDragonCount is passed into IncrementDragons(), which properly works with a reference to the original value before exiting.

 

Here’s what happens with the same example, but missing the ref keyword (Passing By Value):

 

   1: private void Main_Load(object sender, EventArgs e)
   2: {
   3:
   4:     int TotalDragonCount = 1;
   5:     IncrementDragons(TotalDragonCount);
   6:     Console.Write(TotalDragonCount);
   7:
   8: }
   9:
  10: private void IncrementDragons(int OriginalDragonCount) {
  11:
  12:     OriginalDragonCount++;
  13:
  14: }

The output is 1. Notice the difference between value types and reference types? If int was a reference type instead of a value type, this last example would have returned 2.

 

The Takeaway

 

Value types behave very intuitively, but be careful with reference types, especially when passing them By Reference. Know the (subtle) difference between passing value types By Reference and passing reference types By Value (A Value type passed by reference simply points back to the original value type, while a reference type by value creates a copy of the reference (address) inside the method being invoked)

Your email address will not be published. Required fields are marked *

*