Boxing and unboxing with generics
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Boxing converts a value type (like int, double, bool) into a reference type by wrapping it in a heap-allocated object. Unboxing extracts the value type from that object. In C# and Java, boxing happens implicitly when value types are assigned to object or interface references. Generics eliminate most boxing by preserving type information at compile time, but there are subtle cases where boxing still occurs.
Boxing and Unboxing in C#
Each boxing operation allocates memory on the heap and copies the value. Each unboxing operation type-checks and copies the value back. Both are expensive compared to working directly with value types.
The Cost of Boxing
Each Add(i) allocates ~16 bytes on the heap (object header + value). For 1 million ints, that is ~16 MB of unnecessary heap allocations plus GC pressure.
Generics Eliminate Boxing
List<int> uses an internal int[] array. No heap allocation per element, no type-checking on retrieval. The CLR generates specialized code for each value type used with a generic class.
How C# Generics Avoid Boxing (Reification)
C# generics are reified — the runtime generates distinct native code for each value type:
This is different from Java's approach (see below). The generic type parameter is preserved at runtime, enabling the JIT compiler to emit optimized code.
Java Generics and Type Erasure
Java generics use type erasure — the compiler removes type parameters and replaces them with Object at runtime:
In Java, List<int> is impossible — you must use List<Integer>, and every primitive is autoboxed. This is why Java has wrapper classes (Integer, Double, Boolean).
When Boxing Still Happens in C# Generics
Even with generics, boxing occurs in these cases:
1. Calling Interface Methods on Constrained Generics
2. Casting to Object or Interface
3. Using Non-Generic Collections
Benchmarking Boxing vs Generics
Generics are roughly 8x faster and use 6x less memory in this scenario.
C# vs Java Generics Comparison
| Feature | C# Generics | Java Generics |
| Implementation | Reification (runtime types) | Type erasure (compile-time only) |
| Value types | No boxing (List<int>) | Always autoboxing (List<Integer>) |
| Runtime type info | Available (typeof(T)) | Lost (cannot do instanceof T) |
| Primitive support | Direct (List<int>, List<double>) | Wrapper classes only |
| Performance | Better for value types | GC overhead from autoboxing |
Common Pitfalls
- Hidden boxing: Calling
object.ToString(),object.GetHashCode(), orobject.Equals()on a value type through a non-constrained generic parameter may cause boxing. Usewhere T : IEquatable<T>constraints to enable direct dispatch. - Java autoboxing in loops:
for (int i : integerList)unboxes every element. For performance-critical loops with millions of elements, consider usingint[]instead ofList<Integer>. - Null with unboxing: In Java, unboxing a
nullIntegerthrowsNullPointerException. In C#, unboxing anullobjectto a value type throwsNullReferenceException. Always null-check before unboxing. - Dictionary key boxing: In C#, using a
structas aDictionarykey without implementingIEquatable<T>causes boxing on every lookup because the defaultEqualityComparerusesobject.Equals. - Java identity comparisons:
Integer.valueOf(127) == Integer.valueOf(127)istrue(cached), butInteger.valueOf(128) == Integer.valueOf(128)isfalse(different objects). Always use.equals()for wrapper comparisons.
Summary
- Boxing wraps value types in heap objects; unboxing extracts them — both are expensive
- C# generics (reified) eliminate boxing by generating specialized code for each value type
- Java generics (type-erased) always autobox primitives into wrapper classes
- Use
List<int>instead ofArrayListin C# to avoid boxing - Add interface constraints (
where T : IEquatable<T>) to prevent boxing in generic methods - Benchmark with
MemoryDiagnoserto detect hidden boxing allocations

