Skip to content

Boxing / Unboxing

This chapter shows (hidden) pitfalls in terms of boxing and unboxing.

Enumerate through IList<struct> vs List<struct>

When you call List<struct>.GetEnumerator() (which will be done in every foreach loop) you get a struct named Enumerator. When calling IList<struct>.GetEnumerator() you get a variable of type IEnumerator<struct>, which contains a boxed version of your value type. In the performance critical section, this can be important.

Bad This will box every individual integer in the list when enumerated through.

private int GetSum(IList<int> numbers)
{
    foreach (var number in numbers)
    // ...
}

Good Using the List<T> implementation avoids the boxing.

private int GetSum(List<int> numbers)
{
    foreach (var number in numbers)
    // ...
}

💡 Info: The same applies to Collection<struct> and ICollection<struct>. Basically everytime when you use the interface, changes are that the values get boxed. This also happens with LINQ queries.

Benchmark

public class Benchmark
{
    private readonly List<int> numbersList = Enumerable.Range(0, 10_000).ToList();
    private readonly IList<int> numbersIList = Enumerable.Range(0, 10_000).ToList();

    [Benchmark(Baseline = true)]
    public int GetSumOfList()
    {
        var sum = 0;
        foreach (var number in numbersList) { sum += number; }
        return sum;
    }

    [Benchmark]
    public int GetSumOfIList()
    {
        var sum = 0;
        foreach (var number in numbersIList) { sum += number; }
        return sum;
    }
}

Results:

|        Method |      Mean |     Error |    StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
|-------------- |----------:|----------:|----------:|------:|--------:|----------:|------------:|
|  GetSumOfList |  9.392 us | 0.0186 us | 0.0165 us |  1.00 |    0.00 |         - |          NA |
| GetSumOfIList | 44.098 us | 0.3359 us | 0.3142 us |  4.69 |    0.03 |      40 B |          NA |

Using List<struct> in LINQ queries will box the enumerator

As LINQ queries are built upon IEnumerable<T> passing a list of value types to a LINQ query will box the enumerator. This is especially unwanted in high performance path in your application.

Bad Using LINQ query to get the sum of a List<int>.

List<int> numbers = GetNumbers();

var sum = numbers.Sum(); // This will box the enumerator

Good Use foreach with the enumerator of the list to avoid boxing.

List<int> numbers = GetNumbers();

var sum = 0;
foreach (var number in numbers)
    sum += number;

Benchmark

public class Benchmark
{
    private readonly List<int> numbersList = Enumerable.Range(0, 10_000).ToList();

    [Benchmark(Baseline = true)]
    public int GetSumViaForeachList()
    {
        var sum = 0;
        foreach (var number in numbersList)
            sum += number;

        return sum;
    }

    [Benchmark]
    public int GetSumViaLINQ() => numbersList.Sum();
}

Results:

|               Method |      Mean |     Error |    StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
|--------------------- |----------:|----------:|----------:|------:|--------:|----------:|------------:|
| GetSumViaForeachList |  9.391 us | 0.0210 us | 0.0176 us |  1.00 |    0.00 |         - |          NA |
|        GetSumViaLINQ | 43.778 us | 0.1257 us | 0.1176 us |  4.66 |    0.02 |      40 B |          NA |