Wednesday, June 6, 2012

C# Quiz: Working with Value Types

A while ago, I read Jefferey Richter's book CLR via C#. I can say this book is a really great resource for everyone who wants to know how CLR internally works.

One really interesting topic was the difference between reference types (classes) and value types (structs) in .NET.

Here's a little quiz. Give yourself a few seconds to try to figure out what the console output will be. When you think you got it, scroll down and compare your assumptions with the actual outputs.

namespace ConsoleApplication1 {
   interface IValueProvider {
      void SetValue(int i);
   }

   struct MyValueProvider : IValueProvider {
      private int _value;

      public MyValueProvider(int value) {
         _value = value;
      }

      public void SetValue(int value) {
         _value = value;
      }

      public override string ToString() {
         return _value.ToString();
      }
   }

   class Program {
      static void Main(string[] args) {
         MyValueProvider v = new MyValueProvider(1);
         Console.WriteLine(v);

         MyValueProvider v2 = v;
         v2.SetValue(2);
         Console.WriteLine(v);

         IValueProvider i = v;
         i.SetValue(3);
         Console.WriteLine(v);

         object o = v;
         ((MyValueProvider)o).SetValue(4);
         Console.WriteLine(o);

         ((IValueProvider)o).SetValue(5);
         Console.WriteLine(o);
      }
   }
}




Give yourself a few seconds before you scroll down to read the disclosure.




You think you got it?



Sure?



Disclosure

Here is the output to the console:


Kudos to you if you have been 100% correct! If you have been surprised of one or more outputs, let me explain.

Boxing and Unboxing

The main reason for the above output is how CLR does boxing and unboxing.

Reference types (classes) like strings, database connections and most other types in .NET library always live on the application domains managed heap, that is managed by the garbage collector. Value types (structs) like Guids, ints and all other primitives live on the threads local call stack.

Whenever CLR needs a reference of a value type, it needs to create a copy of the local stack memory on the heap (what is called boxing). Whenever a heap reference is cast into a value type on stack, it becomes copied from global heap into a local piece of memory (what is called unboxing).

Now find the next chapter for explanations of the initial quiz.

Explanations

First section:
MyValueProvider v = new MyValueProvider(1);
Console.WriteLine(v);
Shows "1", nothing special no surprise.

Second section:
MyValueProvider v2 = v;
v2.SetValue(2);
Console.WriteLine(v);
Shows "1". MyValueProvider is a struct what means that "v" is no reference to an instance of MyValueProvider on the heap but the instance itself and allocated on the stack. The assignment "v2 = v" causes a copy of MyValueProvider into a new instance on the stack. As a result, any changes of "v2" will not affect "v".

Third section:
IValueProvider i = v;
i.SetValue(3);
Console.WriteLine(v);
Shows "1". MyValueProvider implements the interface IValueProvider. While MyValueProvider is a value type, interfaces are always handled as reference types and so "i" causes a boxing of our value onto the heap. Any changes of the interface reference will not affect the original instance "v".

Fourth section:
object o = v;
((MyValueProvider)o).SetValue(4);
Console.WriteLine(o);
Shows "1". The assignment of "v" with a variable of type Object causes a boxing of the value type onto the heap. "((MyValueProvider)o)" causes a local (stack) de-referencing (unboxing) of the previously heap copied version of our value. Since we now have another, new instance of our value type the rest of the line ".SetValue(4)" will only affect the unboxed instance but will not affect the boxed value on the heap. (If you would try to assign a property instead of calling a method, you would actually get a compiler error, since C# compiler knows that the property assignment would never affect the boxed value type.)

Fifth section:
((IValueProvider)o).SetValue(5);
Console.WriteLine(o);
Shows "5". As explained for third output line, interfaces do always need a reference on the heap. But this time we "o" already represents a heap reference. CLR does not need to create a copy of the boxed value but can use the existing one. The boxed value type becomes cast into IValueProvider and calls the method on it. Therefore a call of the interface method will change the value of our boxed value type instance.

Little Conclusion

Some of the results might look a little spooky for the first glance. However, we use to work with value types all the time and everything usually works fine. There is a general guideline to keep in mind that helps to avoid running into one of the above shown issues.

Value types should always be immutable, what means they should not change their internal values, once they are created. If you keep this in mind you are protected against unexpected boxing/unboxing behavior.