Hidden Costs of Boxing in C#: How to Detect and Avoid Them
C# Boxing and Unboxing are vital players in the performance of an application. However, they are often overlooked. They involve heap allocations that bring a penalty due to their accessing mechanism. In today's post, we will unfold Boxing and Unboxing in detail, study how they are costly to your application, and how to avoid such issues.

What is Boxing and Unboxing in C#?
Boxing refers to converting a value type, such as int
, double
, long
, or struct
into a reference type (e.g., object or interface
). When the common language runtime (CLR) boxes a value type, it wraps the value inside a System. Object
instance and stores it on the managed heap. Heap stores data for a long lifespan, and the Garbage Collector performs allocations and deallocations. In non-boxing, short-lived variables like local variables and method parameters use a stack that works on Last In, First Out (LIFO). Boxing implicitly converts value type variables to objects, as every type is a child of the Object
class.
Unboxing is the opposite of Boxing, where value types are extracted from the object types. Unboxing requires explicit casting to the target type.
Boxing example
int count = 200;
object obj = count;
We boxed the count variable into the obj
instance of type object
.
Unboxing example
object obj = 204;
int count = (int) obj;
Now that we have understood what Boxing is, let's move on to the second part, which is how it affects your application.
Boxing project to evaluate performance difference with BenchmarkDotNet
I am creating a benchmark project to see the impact of Boxing against non-boxing scenarios. If you want to explore benchmarking and the BenchmarkDotnet library, make sure to read through this post: How to Monitor Your App's Performance with .NET Benchmarking.
Step 1: Create a project
dotnet new console -n BoxingBenchmark
cd BoxingBenchmark
Step 2: Install BenchmarkDotNet
dotnet add package BenchmarkDotNet
Step 3: Set up Benchmark runner
using BenchmarkDotNet.Attributes;
namespace BoxingBenchmark;
[MemoryDiagnoser]
public class BoxingBenchmarksRunner
{
private int value = 222;
[Benchmark]
public object WithBoxing()
{
// Boxing: converting value type (int) into object
return value;
}
[Benchmark]
public int WithoutBoxing()
{
// No boxing: stays as int
return value;
}
[Benchmark]
public int WithUnboxing()
{
// Boxing + Unboxing: store as object and cast back
object boxed = value;
return (int)boxed;
}
}
I defined 3 methods: Boxing
, WithoutBoxing
, and WithUnboxing
for a simple int
variable. With the Benchmark
attribute, they will come under benchmarking. By default, BenchmarkDotnNet only evaluates time. Hence, at the top, I have specified MemoryDiagnoser
to include memory in the benchmarking process as well.
Step 4: Run the benchmark runner in Program.cs
using BenchmarkDotNet.Running;
using BoxingBenchmark;
BenchmarkRunner.Run<BoxingBenchmarksRunner>();
Step 5: Build the solution
Benchmark requires a release environment. To release, we need to build the project first.
dotnet build
Step 6: Run release
Now running in release mode
dotnet run -c Release
Result

We can clearly notice that Boxing not only slows down the execution but also consumes memory even for a single int
variable. Without Boxing, a fast stack is used on the LIFO principle. While Boxing uses heap memory to store that value. The allocation and deallocation involve a garbage collector that removes the value from the heap when it is no longer needed. All this takes longer than stack push and pop. However, the garbage collector is itself a program that incurs CPU overhead, along with memory and time penalties. Stack uses optimized CPU cache for small value storage. However, the heap requires accessing slower memory, so it bypasses caching optimizations.
Boxing benchmark to evaluate ArrayList
vs List
One of the most prominent examples of boxing you use without knowing is ArrayList
. Yes, ArrayList
works on boxing behind the scenes. Let's continue with the same project, showing how much using ArrayList
and List
can differ.
Step 1: Create a Benchmark runner
using System.Collections;
using BenchmarkDotNet.Attributes;
namespace BoxingBenchmark;
[MemoryDiagnoser]
public class AppointmentBenchmarksRunner
{
private const int Count = 1000;
[Benchmark]
public void UsingArrayList()
{
var list = new ArrayList();
for (int i = 0; i < Count; i++)
{
// Boxing happens here because ArrayList stores as object
list.Add(i);
// Unboxing when retrieving
var a = (int)list[i];
}
}
[Benchmark]
public void UsingGenericList()
{
var list = new List<int>();
for (int i = 0; i < Count; i++)
{
list.Add(i);
// No boxing/unboxing
var a = list[i];
}
}
}
We defined two methods here. The first one uses an ArrayList
, and the second uses a generic List
to save integer data. To maintain a considerable sample size, I performed 1000 iterations.
Step 2: Run the benchmark runner in Program.cs
using BenchmarkDotNet.Running;
using BoxingBenchmark;
BenchmarkRunner.Run<AppointmentBenchmarksRunner>();
Step 3: Build project
dotnet build
Step 4: Run the project in release
dotnet run -c Release
Result

Step 5: Increase the iteration size
To observe the effect of increasing data, I am increasing the size to 10000.
private const int Count = 10_000;
Result

We can observe that when the size of the data increased by 10 times, the speed of the generic list decreased in the same ratio. However, memory usage increased slightly. By the way, the change in resource usage was predictable. On the other hand, ArrayList
took quite longer in execution, and its memory allocation was about four times that of the generic List
. The List
has no hidden allocations, hence it provides stable scaling. While ArrayList
uses garbage Collector operations for each item, it is expensive.
Where did Boxing work in your project?
Boxing is in many places where you need to know and avoid. I have listed a few common scenarios where you knowingly or unknowingly use boxing.
Using non-generic collections (ArrayList
, Hashtable
)
var list = new ArrayList();
list.Add(26); // boxing
int firstValue = (int)list[0]; // unboxing
Generic collections with interface constraints
var list = new List<IComparable>();
list.Add(42); // boxing, because int stored as IComparable
Even if you are using a generic collection but with interface constraints, it utilizes boxing.
How to avoid Boxing in C#?
As it is expensive in many aspects, we should avoid falling into scenarios that use boxing implicitly or explicitly.
Use the Generics collections
As we have already seen in our examples, using generic collections for value types saves us from boxing pitfalls. Use List<T>
or Dictionary<TKey, TValue>
instead of a collection that relies on boxing, such as ArrayList
.
Avoid unnecessary object casting
Wherever possible, avoid using System.Object
variables in your project. System.Object
require casting into value types that use boxing.
Use Object pooling
Instead of creating new objects every time you need them, create an object pool for heap-allocated objects. It allows you to reuse objects from the pools and relieve the garbage collector from frequent allocation and deallocation jobs. Object pooling technique is essential in real-time systems, high-throughput servers, and other performance-critical applications.
Conclusion
Boxing is a performance penalty that is hidden in a project if you don't choose the right thing for the right place. Mostly, it does not drive you to significant performance issues. However, if your application is very performance-critical and boxing gets involved in operations with extensive data, you can get in trouble. In this post, I shared insights about how much it can affect if you opt for the wrong collection or way of dealing with value types. We benchmarked performance for boxing and unboxing methods. We discussed where you might potentially rely on boxing without knowing. By following the guidance of this post, you can save your project from unnecessary resource usage and latency.
elmah.io: Error logging and Uptime Monitoring for your web apps
This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.
See how we can help you monitor your website for crashes Monitor your website