Programmers new to C# can encounter less than ideal performance and even unexpected results due to boxing and unboxing of value types. This article explains the concept of boxing and unboxing and how its subtle behavior, if not fully understood, can adversely affect application performance and cause unexpected, difficult to locate bugs.
What are Value Types?
Value types are lightweight objects that are allocated on the current thread's stack. (Exceptions to this rule occur when a value type is allocated as an element of an array or is a field of a reference type.) All primitive types (byte, char, int, long, etc.) and structures are value-based types derived from System.ValueType. Because value types are stack-based, they can be created and accessed more efficiently than reference types, which are heap-based and must be accessed using references.
There are situations, however, when a value type needs to behave like a reference type. This occurs whenever a value type instance is converted to either the base "object" type (remember that all value types ultimately derive from "object") or to an interface it implements. A conversion can occur explicitly through a cast operation or implicitly by the compiler during an assignment operation or a method call. When a conversion is to be performed, the C# compiler automatically generates code that causes the value type instance to be wrapped within the context of a reference object. The resulting "boxed" value type instance resides on the managed heap. Conversely, the compiler generates code that causes a boxed value type instance to be unwrapped when explicitly cast to its true value type. This unwrapping operation is referred to as "unboxing."
This is an oversimplification of what actually occurs when a value type instance is boxed or unboxed. Let's explore in greater detail how these operations are actually performed.
How Does Boxing Really Work?
When an instance of a value type is converted to its base "object" type or to an interface it implements, the CLR (Common Language Runtime) allocates enough memory from the managed heap to hold a copy of the value type instance along with the necessary internal data required to create a valid reference object. The CLR then copies the value type instance to the newly allocated area in the managed heap. The following code fragment demonstrates boxing of a value type.
Rectangle myRect=new Rectangle();
object obj=new myRect;
In the above code fragment, the structure myRect is implicitly converted to an instance of type "object" when assigned to the variable obj.
Let's take a look at the IL assembler code emitted by the C# compiler.
ldloca.s myRect
initobj [System.Drawing]System.Drawing.Rectangle
ldloc.0
box [System.Drawing]System.Drawing.Rectangle
stloc.1
The first instruction loads a managed pointer to myRect on the evaluation stack in preparation for initialization. Using the managed pointer, the next instruction (initobj) initializes myRect to all zeroes.
Following initialization, myRect, located in local variable slot 0, is loaded on the stack. The box instruction pops myRect off the stack, creates a new instance of myRect as a reference object on the managed heap, and then pushes the resulting reference on the stack. Finally, the reference is stored in local variable slot 1.
Every method has an internal table used for temporarily storing local variables. This is known as the local variable table. Each entry or slot is identified by a zero-based ordinal number, and is associated with a static type -- static in the sense that the type is set at compile-time and does not change during runtime execution.
Clearly, boxing operations result in significant overhead attributed to heap allocation and copying of the value type's state. Now let's take a look at how unboxing operations work.
How Does Unboxing Really Work?
When an instance of an "object" type or interface, created as a result of boxing, is explicitly converted back to its true value type, the CLR merely returns a managed pointer to the value type instance contained within the reference object. The "unboxed" instance is typically copied to a stack-based instance through an assignment operation. The following code fragment demonstrates unboxing of a value type.
myRect=(Rectangle)obj
Here, we are explicitly converting the variable obj back to the structure myRect.
Now let's look at the IL assembler code emitted by the C# compiler.
IL_000f: ldloc.1
IL_0010: unbox [System.Drawing]System.Drawing.Rectangle
IL_0015: ldobj [System.Drawing]System.Drawing.Rectangle
IL_001a: stloc.0
The first instruction loads a reference to the boxed value type on the stack. The unbox instruction pops the reference off the stack and pushes a managed pointer to the object's contained Rectangle instance on the stack. Next, the ldobj instruction pops the managed pointer off the stack and, using the managed pointer, copies the Rectangle instance on the stack. Finally, the Rectangle copy is stored in local variable slot 0 (the assignment to myRect.)
Note that the boxed value type still exists on the managed heap. This heap object will continue to exist until garbage collection reclaims the space (after no further references to the object exists.)
From the previous example, it's clear that unboxing does not incur the same cost that boxing incurs. However, in many cases, an assignment operation is associated with the conversion. This assignment causes a copy operation to be performed.
Now that we understand what boxing and unboxing is, let's take a look at the subtle problems that can arise from this behavior.
All Is Not As It Seems
It's not always obvious or intuitive when boxing or unboxing occurs. Nor are the potential side effects readily understood. You may encounter a situation where updating an instance of a value type does not actually update the instance at all. You're probably wondering what I'm talking about. Let me explain using an example.

In this example, an ArrayList is created to hold five items. The first loop creates and adds five Rectangle instances, each with a width and height of 100 pixels, to the ArrayList instance. The second loop increases the width and height of each Rectangle instance by an additional 400 pixels. Finally, the last loop merely prints out the values of each Rectangle instance.
This code appears quite trivial at first glance. However, let's take a look at the output.

The widths and heights of all the Rectangle instances are still 100 pixels! What happened to our updates? To answer this question we need to look at the IL assembler code of the second loop.

Statement IL_0035 unboxes the reference object retrieved from the ArrayList by placing a managed pointer to the contained Rectangle instance on the evaluation stack. The next statement, IL_003a, pops the managed pointer off the stack, copies the Rectangle instance it points to, and pushes this copy on the stack.
From this point on, we are working with a temporary, stack-based copy of the Rectangle instance rather than the heap-based instance referred to by the ArrayList. As we look at the remainder of the IL assembler code, you'll notice that the boxed Rectangle instance never gets updated with the stack-based copy. Therefore, when we print the contents of the ArrayList we see only their original values.
This example demonstrated how boxing and unboxing can cause subtle programming errors that are difficult to locate (even when using a debugger.) Next, we look at how boxing and unboxing can affect application performance.
Performance Cost
It's not always obvious to those new to C# where boxing may be occurring in their applications. Nor is it obvious that these boxing operations may be affecting their application's performance.














Join the conversation! Your thoughts help the community grow.