All writing

Deep Dive into C# Boxing and Unboxing

A practical guide to boxing and unboxing in C# and their performance impact.

Updated 7 min read
Examples checked with .NET 10.0.12
Text size

Search article sections

Search across the full articles and jump to a matching section. Try “composite cursor”, “IMiddleware”, or “round robin”.

C# to ASP.NET Core · Step 1 of 2

Before you start: C# classes, methods, and collections; Basic HTTP requests and responses.

  1. C# Boxing and Unboxing (you are here)
  2. 3 Ways to Build Custom Middleware in ASP.NET Core
View this reading path

Understanding the Fundamentals

Boxing and unboxing describe how the .NET runtime moves data between value types and reference types.

This behavior is rooted in how the CLR represents data in memory and how the JIT compiler generates machine code.

C# Boxing and Unboxing Overview
C# Boxing and Unboxing Overview

C# Boxing and Unboxing Overview

Value Types vs Reference Types

Value types

  • int, double, bool
  • struct, enum
  • Stored inline
  • Copied by value
  • Designed for small, immutable data

Reference types

  • class, string, object
  • Stored on the managed heap
  • Passed by reference
  • Require garbage collection

What Is Boxing

Boxing is the process of wrapping a value type inside a reference type (object or interface).

C#
int number = 42;
object boxed = number;

At runtime, this causes the CLR to allocate a new object on the managed heap and copy the value into it.

What Is Unboxing

Unboxing extracts the value type from the boxed object.

C#
object boxed = 42;
int unboxed = (int)boxed;

Unboxing is not just a cast. It includes a runtime type check and a memory copy.


What Actually Happens During Boxing

C#
int number = 42;
object boxed = number;

Internally, the CLR performs the following steps:

  1. Allocates memory on the managed heap
  2. Writes an object header (method table pointer, sync block)
  3. Copies the value into the object payload
  4. Returns a reference to the object

Although int is only 4 bytes, the boxed object typically occupies 24 bytes or more, depending on platform and alignment.

Why Size Increases

A boxed value includes:

  • Object header
  • Type metadata pointer
  • Padding for alignment
  • The actual value

This explains why boxing can dramatically increase memory usage in tight loops or large collections.


What Actually Happens During Unboxing

C#
object boxed = 42;
int value = (int)boxed;

The CLR performs:

  1. A runtime type check to ensure the object contains the expected value type
  2. Copies the value from the heap back to the stack or register
  3. Leaves the boxed object on the heap for later garbage collection

Unboxing does not free memory. The boxed object remains until collected by the GC.

If the runtime type does not match, an InvalidCastException is thrown.


Memory Management

Stack vs Heap in .NET
Stack vs Heap in .NET

Stack vs Heap in .NET

Stack vs Heap in Practice

Stack

  • Extremely fast allocation
  • Automatic cleanup
  • Limited size
  • Used for local value types and method frames

Heap

  • Slower allocation
  • Managed by garbage collector
  • Larger and flexible
  • Used for reference types and boxed values

Example Memory Layout

C#
int x = 10;
object boxed = x;

  • x lives inline in the stack frame
  • boxed is a reference pointing to heap memory
  • The value 10 is duplicated, not shared

This duplication is a key reason boxing should be avoided in performance-sensitive paths.


Why Boxing Hurts Performance

Boxing introduces multiple hidden costs:

  • Heap allocation
  • Additional memory usage
  • CPU cycles for copying
  • Garbage collection pressure
  • Cache inefficiency

Unboxing adds:

  • Runtime type checking
  • Additional memory copy

These costs are often invisible in small programs but become severe in:

  • Hot loops
  • High-throughput services
  • Real-time systems
  • Large collections

Performance Analysis

Boxing Performance and Memory Allocation
Boxing Performance and Memory Allocation

Boxing Performance and Memory Allocation

Boxing in Collections

Compare the same loop with an object-based collection and a generic collection:

ArrayList (boxing)

ArrayListExample.cs
ArrayList list = new ArrayList();
for (int i = 0; i < 1_000_000; i++)
{
    list.Add(i);
}

Each Add call boxes int into object.

List<int> (no boxing)

GenericListExample.cs
List<int> list = new List<int>();
for (int i = 0; i < 1_000_000; i++)
{
    list.Add(i);
}

The generic version:

  • Avoids boxing entirely
  • Uses contiguous memory
  • Is easier for the JIT to optimize

Memory Footprint Comparison

TypeUnboxed SizeBoxed SizeApprox Increase
bool1 byte~24 bytes24x
int4 bytes~24 bytes6x
long8 bytes~24 bytes3x
decimal16 bytes~40 bytes2.5x

These numbers explain why legacy non-generic APIs scale poorly.


Common Boxing Scenarios

Explicit Boxing

C#
object o = 10;

Implicit Boxing

C#
Console.WriteLine(10);

The WriteLine(object) overload causes boxing.

Interface Boxing

C#
IComparable c = 10;

Value types implementing interfaces are boxed unless constrained generically.

Enum Boxing

C#
Enum e = DayOfWeek.Monday;

Enums are value types and get boxed when treated as Enum or object.


String Interpolation and Boxing

C#
int value = 42;
string s = $"Value is {value}";

This may cause boxing depending on overload resolution.

Safer alternative:

C#
string s = $"Value is {value:D}";

Or:

C#
string s = string.Create(
    CultureInfo.InvariantCulture,
    $"Value is {value}"
);

In high-frequency logging, prefer structured logging frameworks that avoid boxing.


Performance Optimization Example

C#
const int count = 1_000_000;
 
// No boxing
int sum1 = 0;
for (int i = 0; i < count; i++)
{
    sum1 += i;
}
 
// Boxing
object sum2 = 0;
for (int i = 0; i < count; i++)
{
    sum2 = (int)sum2 + i;
}

The second loop:

  • Boxes on every iteration
  • Allocates millions of objects
  • Triggers frequent GC cycles

This pattern is a common hidden performance bug.


Best Practices

  • Prefer generics everywhere
  • Use List<T>, Dictionary<TKey,TValue>
  • Implement IEquatable<T> on structs
  • Keep structs small and immutable
  • Use profilers to detect boxing

Avoid

  • ArrayList, Hashtable
  • APIs that accept object unnecessarily
  • Structs implementing non-generic interfaces
  • Passing value types through object pipelines

Advanced Scenarios

Designing Structs Correctly

C#
public readonly struct Money : IEquatable<Money>
{
    private readonly decimal amount;
 
    public Money(decimal amount)
    {
        this.amount = amount;
    }
 
    public bool Equals(Money other) => amount == other.amount;
 
    public override bool Equals(object obj)
    {
        return obj is Money other && Equals(other);
    }
 
    public override int GetHashCode() => amount.GetHashCode();
}

Implementing IEquatable<T> avoids boxing during equality checks in generic collections.

Generic Constraints to Prevent Boxing

C#
public class Processor<T> where T : struct
{
    public T Process(T value)
    {
        return value;
    }
}

The struct constraint allows the JIT to generate boxing-free code paths.


Modern C# Features That Reduce Boxing

  • Generics
  • Span<T> and ReadOnlySpan<T>
  • Nullable value types (int?)
  • ValueTask
  • Pattern matching with generics

When used correctly, modern C# allows writing allocation-free code in most scenarios.


Summary

Boxing and unboxing are fundamental CLR behaviors that directly impact performance and memory usage.

They are acceptable in:

  • Low-frequency code
  • Application boundaries
  • Debug or tooling scenarios

They should be avoided in:

  • Hot paths
  • Tight loops
  • High-throughput services
  • Allocation-sensitive systems

Understanding boxing is not optional for performance-critical .NET development. It is a core part of writing efficient, scalable C# code.

Verified Examples

Checked on September 16, 2026 with .NET 10.0.12 using SDK 10.0.112. The functional checks cover copying a value into a box, unboxing it to the exact stored type, rejecting an invalid cast, and preserving the same values in ArrayList and List<int>.

The verification harness is scripts/verify-blog/dotnet/Program.cs in the portfolio repository. These checks exercise behavior; allocation sizes and performance measurements require their own workload and runtime configuration.

C# to ASP.NET Core

Next: 3 Ways to Build Custom Middleware in ASP.NET Core
ShareTwitterLinkedInReddit