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>
andICollection<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 |