In this next article in our series of creating optimized .NET code, we’ll discuss “boxing and unboxing”. In case you are unfamiliar with these terms, let’s take a brief look at what boxing and unboxing means.
You can convert value types to reference types and back again. When a value type variable needs to be converted to a reference type, an object (a box) is allocated on the managed heap to hold the value and its value is copied into the box. This process is known as boxing. Boxing can be implicit or explicit, as shown in the following code.
int p = 123;
box = p; // Implicit boxing
box = (Object)p; // Explicit boxing with a cast
Boxing mostly occurs when passing a value type to a method that takes an object as parameter. When a value in an object is converted back into a value type, the value is copied out of the box and into the appropriate storage location. This process is known as unboxing.
p = (int)box; // Unboxing
Boxing issues are exacerbated in loops or when dealing with large amount of data such as large-sized collections storing value types.
Boxing causes a heap allocation and a memory copy operation. To avoid boxing, do not treat value types as reference types. Avoid passing value types in method parameters that expect a reference type. Where boxing is unavoidable, to reduce the boxing overhead, box your variable once and keep an object reference to the boxed copy as long as needed, and then unbox it when you need a value type again.
int p = 123;
box = (object)p; // Explicit boxing with a cast
//use the box variable instead of p
Collections store only data with base type as Object. Passing value types such as integers and floating point numbers to collections causes boxing. A common scenario is populating collections with data containing int or float types returned from a database. This can cause excessive overhead in the case of collections due to iteration. Consider this snippet:
ArrayList al = new ArrayList();
for (int i=0; i<1000;i++)
al.Add(i); //Implicitly boxed because Add() takes an object
int f = (int)al; // The element is unboxed
To prevent this, consider using an array instead, or creating a custom collection class for your specific value type. You must perform unboxing with an explicit cast operator.
There are several ways to measure the impact of boxing operations. You can use Performance Monitor to measure the performance impact of boxing overhead on the resource utilization and response times for your application. To do a static analysis of where exactly you are affected by boxing and unboxing in your code, you can analyze MSIL code. Search for box and unbox instructions in MSIL by using the following command line.
Ildasm.exe yourcomponent.dll /text | findstr box
Ildasm.exe yourcomponent.dll /text | findstr unbox
However, you must watch out where exactly you optimize the boxing overhead. The overhead is significant in places where there are frequent iterations such as loops, inserting, and retrieving value types in collections. Instances where boxing occurs only once or twice are not worth optimizing.
Use the DirectCast operator to cast up and down an inheritance hierarchy instead of using CType. DirectCast offers superior performance because it compiles directly to MSIL. Also, note that DirectCast throws an InvalidCastException if there is no inheritance relationship between two types.
In our next article, we’ll discuss Exception Management.