Monday 10 August 2015

Boxing, is, as

When you cast a value type as reference type this is called boxing, when you convert a reference type type to a value type this is called unboxing. Value types are stored on the stack, whereas reference types are stored on the heap and a reference to that heap value is stored on the stack. Think of the stack as a list, if something is small and simple it goes onto the list, if it's big and complex and doesn't fit on the list, we write down an address as to where we can find the complex thing on our list. 

Unboxing is where you can fall into some troubled waters; as long the type you're unboxing to is correct everything is fine, but should you get it wrong you'll get an invalidCastException during, which can suck.


namespace pav.boxing
{
    class Program
    {
        static void Main(string[] args)
        {
            // boxing
            object o = 32;

            // unboxing
            int i = (int)o;

            //throws System.InvalidCastException
            bool b = (bool)(o);
        }
    }
}


Luckily we have the "is" comparison, which we can do on reference types such as object allowing us to test and see if our reference type is in fact the value type we want to convert to.


namespace pav.boxing
{
    class Program
    {
        static void Main(string[] args)
        {
            // boxing
            object o = 32;

            if (o is bool)
            {
                //this will never fire because our object is not a bool
                var b = (bool)o;
            }
        }
    }
}


The "is" comparison also works when comparing a reference type to another reference type,


namespace pav.boxing
{

    class Person { }

    class Employee : Person { }

    class Program
    {
        static void Main(string[] args)
        {
            object o = 32;

            if (o is int) //true
                Console.WriteLine("o is an int");
            else
                Console.WriteLine("o is not an int");

            if (o is Person) //false
                Console.WriteLine("o is a person");
            else
                Console.WriteLine("o is not a person");

            var p = new Person();
            var e = new Employee();

            if (e is Person) // true
                Console.WriteLine("e is a Person");
            else
                Console.WriteLine("e is not a person");

            if (p is Employee) // false
                Console.WriteLine("p is an employee");
            else
                Console.WriteLine("p is not an employee");
        }
    }
}


Not only can you use it to identify if your variable is of a particular type but also if it's of a base type, in our example, our instance of Employee e is a Person but our instance of Person p is not an Employee. Keep in mind that when casting between reference types, this is not considered 'boxing' or 'unboxing' these terms are reserved for value types to reference types and vice-versa. When converting between reference types it is considered 'upcasting' or 'downcasting' this is because at no point is new memory allocated. 

Let's take a quick look at the "as" operator, the as operator lets us convert variables into nullable types.


static void Main(string[] args)
{
    object o = 32;
   
    //build time error, not allowed because int is not nullable
    var i = o as int;
}

since value types are not nullable, they can't be converted by using the "as" operator. however reference types are nullable.


namespace pav.boxing
{
    class Person { }

    class Employee : Person { }

    class Program
    {
        static void Main(string[] args)
        {
            object o = new Employee();

            var e = o as Employee;
            if (e != null)
                Console.WriteLine("o is an employee");

            var p = o as Person;
            if (p != null)
                Console.WriteLine("o is a person");

            e = new Person() as Employee;
            if (e != null)
                Console.WriteLine("e is an employee");
            else
                Console.WriteLine("e is not an employee");
        }
    }
}


so when we successfully convert a type using the as operator we receive our target type, however is the conversion fails we get a null. A subtype can be converted into a base type, but not vice-versa.

To sum it up, in C#, 'boxing' is to the process of converting a value type (such as an int or a struct) to a reference type (such as an object). This is done by creating a new object and copying the value of the value type into the new object. 'Unboxing' is the opposite process, where a reference type (such as an object) is converted back to a value type. This is done by copying the value stored in the object back into a value type variable.

Boxing and unboxing can have a performance cost, as they involve creating and manipulating objects in memory. Therefore, it is generally recommended to avoid unnecessary boxing and unboxing in performance-critical code.

One thing to keep in mind is that technically since strings and classes are already reference types, they cannot be 'boxed'. Only value types such as int, float, structs, etc can be 'boxed', this is because they are stored on the stack and not the heap like reference types.

When you try to box a reference type, it will just return a reference to itself and does not create a new object. However, even though reference types cannot be boxed, reference types can be stored as objects, just keep in mind that this does not the qualify as 'boxing'. When casting a reference type such as a string or a class to an object this is considered downcasting, whereas converting an object back to a string or class is considered upcasting.