Notice: this article is a work in progress.

The Common Language Runtime

Just like C++, CLR-based languages have their own runtime environment. To keep it short, some of the most important features of the CLR are the garbage collection and the Common Type System. Let’s explore them!

The Common Type System

All CLR-enabled languages use (and abide by) a specific type structure. In layman terms, the Structure you wrote in VB.NET matches exactly its C# counterpart struct, with no difference. This means that every language is virtually interchangeable; the libraries you wrote in a specific language can be reused by any other CLR consumer.

The CTS features a root Type, object. object exposes a few basic methods whose presence permeates and defines the BCL (Base Class Library) in its implementation. Everything can be cast to and from an object.

While everything descends from object, the behaviour of variables changes greatly whether they are value types or reference types.

Reference Types

Reference types live on the managed heap. Whenever you new an instance of a ref type, all you’re really getting is a reference to the newly allocated object. However, don’t let the name mislead you: reference types are passed by value by default, and by reference with the ref operator. We’ll see what passing by value and passing by ref(erence) means in a moment. Casting a reference type to an object and vice versa has no particular effect other than telling the CLR to perform said cast. Typical reference types are strings (even if immutable ones) and collections (such as Lists) - there are of course more throghout the BCL. Reference types are classes in C#.

Value Types

Value types derive from ValueType, which in turn derives from object. Deriving from object is the only thing in common with ref types. Typical value types are int (System.Int32 alias), float (System.Single alias) and so on. They are defined with the struct keyword in C#. Their behaviour is substantially different from their referenced siblings and worth close examination. Value types are generally immutable and they can live on the stack or on the heap. If you define a local value type variable, it will live on the stack. A value type member of a class will live on the managed heap along with the parent object.

Now for the important part. Since ValueType descends from object, it is perfectly legal to cast a struct type to object. However, there’s more to it than the cast syntax shows. Let’s make a practical example:

 
System.Int32 val = 5;         // a fashionable way to declare a int variable
object boxedValueType = (object)val;
int unboxedValueType = (int)boxedValueType;
 

“What’s the deal with boxes?”, you might be thinking by now. Well, this is what happens when you try to cast a struct to an object and vice versa, namely boxing and unboxing. You can actually picture this operation as putting some content into a box, and then extracting this content when necessary. However, as much as casting can come in handy, it comes with a cost: and it is something you want to avoid when dealing with performance critical applications such as... you guessed it, games. What really happens is that the content of the value type is copied into a dummy object (a reference type) allocated on the heap, and then extracted whenever you need the original content of the value type.

Now for a useful tip. Consider the following ‘harmless’ code:

 
int a = 1, b = 2, c = 3;
 
string str = string.Format("{0} {1} {2}", a, b, c);
 

string.Format has a string, params object[] overload. Now, since params is just a handy way to type a variable number of arguments, for argument’s sake let’s just consider object[] as the important part of the method signature.

What really happens is this:

 
int a = 1, b = 2, c = 3;
 
string str = string.Format("{0} {1} {2}", new object[] { (object)a, (object)b, (object)c });
 

A-ha! Nasty. What should we do in order to avoid the boxing and the consequent unboxing happening inside string.Format? Luckily there’s a simple solution:

 
int a = 1, b = 2, c = 3;
 
string str = string.Format("{0} {1} {2}", a.ToString(), b.ToString(), c.ToString());
 

However, we aren’t done with suprises yet. What if we had a user-defined value type?

 
struct MyValueType
{
   public float A, B;
}
 
MyValueType val;
string str = string.Format("Formatted {0}", val.ToString());
 

It all looks fine and dandy, but guess what? Whoopsie, daisy. val is going to get boxed into an object. Why? Easy solution, again.

 
struct MyValueType
{
   public float A, B;
 
   //added code here
   public override string ToString()
   {
      return string.Format("{0} and {1}", A.ToString(), B.ToString());
   }
}
 

As you can see, our original MyValueType value-type didn’t have a ToString override. So, every time you invoked ToString(), you would implicitly box the value type to object and call the default implementation of ToString. The moral of the story? Make sure you provide overrides for GetHashCode and ToString for your value-types.

The Garbage Collection

As the name implies, the garbage collection takes care of disposing of unneeded objects living on the managed heap. This means that the developer is relieved from manual memory deallocation, but not from memory management.

 
clr_and_csharp.txt · Last modified: 2013/11/22 13:32