Blog Series Tags

C# Value Types are Objects

Value types in C# are Objects, but they don’t behave exactly like Objects do. A part of the reason for this is that all value types are implicitly derived from System.ValueType.

public abstract class ValueType
{
    protected ValueType();
    public override bool Equals(object obj);
    public override int GetHashCode();
    public override string ToString();
}
But wait! Structs can’t derive from classes!

At first glance, this seems true. You can’t for example do this:

public class Animal { }
public struct Rat : Animal { }

The compiler wont let you. Yet if MSDN and the C# language specification itself is to be belived, all value types inherit from object across System.ValueType, but Int32 doesn’t seem to mention this fact in its definition.

public struct Int32 : IComparable, IFormattable, IConvertible, IComparable<Int32>, IEquatable<Int32>

The reason for this is that extending a struct from a class is only allowed in the special case of System.ValueType. Int32 cannot inherit from any other type and so the language designers thought that it makes no sense to explicitly mention this. This does however make the language seem magical, when the base value types suddenly seem to extend from something it does not mention.

But wait! Objects live on the heap, value types on the stack

Not always true, and more importantly this is an implementation detail of the C# language runtime, rather than a semantic obervation of value types. Certainly for the most part, value types may reside on the stack, but this is not always the case and the runtime might change the alocation behavior of the type depending on usage. Consider captured variables in anonymous methods.

public static Action<int> CreateIncrementor()
{
    int count = 0; // (1)
    Function<int> doIncrement = () => { return count++; } // (2)
    return doIncrement; // (3)
}

// Elsewhere
var doIncrement = CreateIncrementor();
var incrementedValue = doIncrement(); // (4)

The outer variable “count” is initialised in (1), then used inside the lambda expression in (2), returned in (3) and used in (4) when the “doIncrement” lambda function is called. If the variable was stored on the stack it would dissapear when “CreateIncrementor” returned. It does not, and so it is probably not stored on the stack. The compiler inlines the value type into an annoymous class of its own and places an instance of that class on the heap along with a reference to it from the “doIncrement” lambda. This too is an implementation detail and may change, what’s important however is the obeservation that there is no limitation on value types to be stored on the stack and perhaps more importantly the need to distinguish between the implementation and langauge definition.

Coming back to the argument on objects and value types, given that it is no longer nessesary for value types to occupy the stack and given that that argument isn’t valid from a language definition perspective anyway, value types can be objects and be stored whereever the implementation decides (though most likely that will be the stack)

What about boxing and GetType()?

For the most part, value types are stored and copied by value. An Int32 would be 4 bytes of data residing somewhere. Those 4 bytes would have a distinct, meaning based on the type of data being represented. In the case of Int32, it would be a number.

On the other hand, reference types are stored and copied by reference. A variable of a reference type is a pointer to some location in memory that contains a memory layout for the object instance. The pointer itself has a semantic meaning of a… pointer, while the bytes that store the semantic meaning of the state of the object reside elsewhere.

Boxing is a way of unifying these two worlds. By boxing an Int32, you allow it to reside in the same realm as other objects.

int a = 10;
object o1 = (object)a; // Explicit boxing
object o2 = a; // Implicit boxing
int b = (int) o1;

Boxing is the “There and Back Again” of types in C#, the lowly Hobbit becomes an intepred adventure and returns bearing syphylis… but I digress.