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.