c# – Boxing ValueType when using IEnumerable

Question:

Let's say you have an array, for example:

int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } };

All arrays implement IEnumerable (not generic), so when using this interface all elements will be boxed?

The question is relevant, for example, when using the Linq -operations Cast<T>() or OfType<T>() :

Console.WriteLine(string.Join(" " , array.Cast<int>()));

Answer:

Yes, the situation with multidimensional arrays is pretty sad. Such an array implements IEnumerable but does not implement IEnumerable<T> . This means that any use of a multidimensional array through the "prism" of IEnumerable will result in packing each element, and using the Enumerable.Cast<T> method is no exception.

Here's a simple benchmark (based on BenchmarkDotNet ) that shows that this is true:

[MemoryDiagnoser]
public class MultidimentionalAarrayTests
{
    private int[,] m_multiArray = {{1, 2}, {3, 4}};
    private int[] m_regularArray = {1, 2, 3, 4};

    [Benchmark]
    public int MultiArrayLast()
    {
        return m_multiArray.Cast<int>().Last();
    }

    [Benchmark]
    public int RegularArrayLast()
    {
        return m_regularArray.Last();
    }
}

Result:

                     Method |        Mean |     Error |    StdDev |  Gen 0 | Allocated |
--------------------------- |------------:|----------:|----------:|-------:|----------:|
             MultiArrayLast | 1,166.97 ns | 23.229 ns | 51.473 ns | 0.0401 |     132 B |
           RegularArrayLast |    51.29 ns |  1.250 ns |  3.686 ns |      - |       0 B |

We see a bunch of allocations here: in the first case, each element is packed, an iterator in Cast<T> , an iterator in Last<T> . In the second case, there are no allocations at all, since Last<T> checks that the sequence implements IList<T> (and the one-dimensional array does) and immediately returns the last element.

Since multidimensional arrays do not implement the generic IEnumerable<T> , we cannot force it to do it ourselves, but we can create an extension method so as not to use Enumerable.Cast<T> :

public static class MultiDimentionalArrayEx
{
    public static IEnumerable<T> AsEnumerable<T>(this T[,] array)
    {
        foreach (var e in array) yield return e;
    }
}

Now we can add another benchmark to test the result:

[Benchmark]
public int MultiArrayWithAsEnumerable()
{
    return m_multiArray.AsEnumerable().Last();
}

And here's the final result:

                     Method |        Mean |      Error |     StdDev |  Gen 0 | Allocated |
--------------------------- |------------:|-----------:|-----------:|-------:|----------:|
             MultiArrayLast | 1,115.45 ns | 31.0145 ns | 90.9603 ns | 0.0401 |     132 B |
           RegularArrayLast |    46.11 ns |  0.1826 ns |  0.1525 ns |      - |       0 B |
 MultiArrayWithAsEnumerable |   161.74 ns |  3.2693 ns |  3.2109 ns | 0.0150 |      48 B |

Here we see that there is a heap allocation of two iterators (one for the extension method and one for Enumerable.Last<T> ), but no boxing of the elements themselves.

Scroll to Top