Boxing and unboxing: when does it come up?
So I understand what boxing and unboxing is. When's it come up in real-world code, or in what examples is it an issue? I can't imagine doing something like this example:
int i = 123;
object o = i; // Boxing
int j = (int)o; // Unboxing
...but that's almost certainly extremely oversimplified and I might have even done boxing/unboxing without knowing it before.
It's much less of an issue now than it was prior to generics. Now, for example, we can use:
List<int> x = new List<int>();
x.Add(10);
int y = x[0];
No boxing or unboxing required at all.
Previously, we'd have had:
ArrayList x = new ArrayList();
x.Add(10); // Boxing
int y = (int) x[0]; // Unboxing
That was my most common experience of boxing and unboxing, at least.
Without generics getting involved, I think I'd probably say that reflection is the most common cause of boxing in the projects I've worked on. The reflection APIs always use "object" for things like the return value for a method - because they have no other way of knowing what to use.
Another cause which could catch you out if you're not aware of it is if you use a value type which implements an interface, and pass that value to another method which has the interface type as its parameter. Again, generics make this less of a problem, but it can be a nasty surprise if you're not aware of it.
Boxing (in my experience) usually occurs in these cases:
- A value type is passed to a method that accepts an argument of type
Object
. - A value type is added to a non-generic collection (like an
ArrayList
).
Other times you can see boxing and unboxing is when you use reflection as the .NET framework's reflection API makes heavy use of Object
.
Boxing/unboxing occurs when a value type (like a struct, int, long) is passed somewhere that accepts a reference type - such as object
.
This occurs when you explicitly create a method that takes parameters of type object that will be passed value types. It also comes up when you use the older non-generic collections to store value types (typically primitives).
You will also see boxing occuring when you use String.Format()
and pass primitives to it. This is because String.Format()
accepts a params object[] - which results in boxing of the additional parameters in the call.
Using reflection to invoke methods can also result in boxing/unboxing, because the reflection APIs have no choice but to return object
since the real type is not known at compile time (and the Reflection APIs cannot be generic).
The newer generic collections do not result in boxing/unboxing, and so are preferable to the older collections for this reason (eg ArrayList, Hashtable, etc). Not to mention they are typesafe.
You can avoid boxing concerns by changing methods that accept objects to be generic. For example:
public void string Decorate( object a ) // passing a value type results in boxing
{
return a.ToString() + " Some other value";
}
vs:
public void string Decorate<T>( T a )
{
return a.ToString() + " some other value";
}
Here is a really nasty one :)
SqlCommand cmd = <a command that returns a scalar value stored as int>;
// This code works very well.
int result = (int)cmd.ExecuteScalar();
// This code will throw an exception.
uint result = (uint)cmd.ExecuteScalar();
The second execute fails because it tries to unbox an Int32 into an UInt32 which is not possible. So you have to unbox first and than cast.
uint result = (uint)(int)cmd.ExecuteScalar();
Boxing and unboxing is really moving from value type to reference type. So, think of it as moving from the stack to the heap and back again.
There certainly are cases where this is relevant. The inclusion of generics in the 2.0 framework cut a lot of common boxing cases out of practice.