In this article, we are going to talk about boxing and unboxing in C#.
When we work with C# language constantly, we need to convert data between different data types. For this reason, sometimes we have to use boxing and unboxing when we need to convert a value type to an object type or vice versa.
So, let’s discuss each of these concepts with some examples.
Overview of Value Types and Reference Types
C# is a type-safe programming language containing value types and reference types. Each of them is allocated in different portions of memory: stack and heap.
A value type variable directly contains its data and holds the data within its own memory location. On the other hand, a reference type variable stores a reference to their data (objects). It contains a pointer to another memory location that holds the real data.
In C#, all types inherit from System.Object
type, directly or indirectly. This means it’s possible to assign values of any type to object
type variables.
NOTE: The object type is an alias to System.Object
in .NET, and an object variable is always a reference type.
There are built-in value types in C# like int
, bool
, double
, and char
among others, that correspond to the primitive types in .NET. Among built-in reference types, we have string
, array
, and especially object
.
Boxing
Boxing is the process to perform a conversion of a value type to an object type and is an implicit process.
Let’s see an example of boxing from an int
variable to an object
:
int valueType = 25; object objectType = valueType;
Here we declare an int
variable named valueType
and initialize it with the value of 25. After that, we create an object variable named objectType
and assign the int
value. As result, the value type variable valueType
is copied to the reference type
variable objectType:
Boxing is implicit because we can do it directly:
object objectType = valueType;
Actually, we also can perform the boxing explicitly, but it is never required:
object objectType = (object)valueType;
We also can perform boxing through a method:
public object BoxToInt(int value) => value;
In this method, we have an int
parameter and we return it as an object type.
Unboxing
Unboxing is basically the opposite of boxing. It’s the conversion from an object type to a value type and unlike the boxing process, it is an explicit process.
Let’s consider the same int
variable type, that we used in the boxing example, to perform the unboxing:
int valueType = 25; object objectType = valueType; //boxing int unboxedValue = (int)objectType //unboxing
We declare a new int variable named unboxedValue
, and we typecast the objectType
variable to an int value:
In this case, the conversion must be explicit because the conversion from object to int needs a type casting:
(int)objectType
Otherwise, we’ll have a compiler error CS0266:
Cannot implicitly convert type 'object' to 'int'. An explicit conversion exists (are you missing a cast?)
Another important point! We can’t perform an unboxing of a boxed variable to a different type:
int valueType = 25; object objectType = valueType; //boxing short unboxedValue = (short)objectType; //unboxing
In this case, we get a System.InvalidCastException:
Unable to cast object of type 'System.Int32' to type 'System.Int16'.
Finally, we also can use methods for performing unboxing of different types:
public int UnboxToInt(object o) => (int)o;
So, here we do the reverse process, where we receive the parameter o
, which is of object
type and as result, we have an int
value.
Additional Info About Boxing and Unboxing
When boxing occurs an object reference is created on the stack memory that references a value of the type on the heap memory.
On the other hand, the unboxing copies the value from the instance into a value type variable.
Both are expensive computational processes, so it is highly recommended to use them just when necessary.
Another care to be taken is that sometimes we could perform, for example, an unboxing without us noticing. It’s the case of the String.Concat
method, that is used frequently:
string name = "Joe Doe"; Console.WriteLine(string.Concat(name,"'s age: ", 31)); /* Output: Joe Doe's age: 33 */
Here we concatenate three variables, two of the string
type and one of the int
type.
But, if we inspect the definition of the Concat
method:
public static string Concat(object? arg0, object? arg1, object? arg2) => Concat(arg0?.ToString(), arg1?.ToString(), arg2?.ToString());
We can see it has twelve different overloads, but paying attention to this specific case, which corresponds to what we are using, we’ll see that all parameters are of the object type and there are unboxing processes happening here.
Let’s consider another example using an ArrayList:
var myArrayList = new ArrayList(); myArrayList.Add(1); // int value myArrayList.Add("Joe Doe"); // string value myArrayList.Add(1.23); // double value foreach (var item in myArrayList) Console.WriteLine(item);
Again, inspecting the definition of the ArrayList.Add
method:
public virtual int Add(object? value)
We can notice an unboxing here.
It will happen the same using a Stack, Queue, or Hashtable:
public virtual void Push(object? obj) // Stack's method public virtual void Enqueue(object? obj) // Queue's method public virtual void Add(object key, object? value) // Hashtable's method
Conclusion
In this article, we have learned about boxing and unboxing in C#, and how to use both. Also, we’ve mentioned some additional info that we need to consider while working with different C# classes or methods.