Допустим, имеется некий массив, например:
int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } };
Все массивы реализуют IEnumerable (не generic), таким образом, при использовании этого интерфейса все элементы будут упакованы?
Вопрос актуален, например, при использовании Linq-операций Cast<T>() или OfType<T>():
Console.WriteLine(string.Join(" " , array.Cast<int>()));
Да, ситуация с многомерными массивами довольно печальная. Такой массив реализует IEnumerable, но не реализует IEnumerable<T>. А это означает, что любое использование многомерного массива через "призму" IEnumerable приведет к упаковке каждого элемента, и использование метода Enumerable.Cast<T> - не исключение.
Вот простой бенчмарк (на основе BenchmarkDotNet), который показывает, что это действительно так:
[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();
}
}
Результат:
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 |
Мы тут видим кучку аллокаций: в первом случае - упакован каждый элемент, итератор в Cast<T>, итератор в Last<T>. Во втором случае нет аллокаций вообще, поскольку Last<T> проверяет, что последовательность реализует IList<T> (а одномерный массив его реализует) и сразу же возвращает последний элемент.
Поскольку многомерные массивы не реализуют обобщенный IEnumerable<T>, то заставить его сделать это самим мы не можем, но мы можем создать метод расширения, чтобы не использовать Enumerable.Cast<T>:
public static class MultiDimentionalArrayEx
{
public static IEnumerable<T> AsEnumerable<T>(this T[,] array) where T:struct
{
foreach (var e in array) yield return e;
}
}
Теперь мы можем добавить еще один бенчмарк, чтобы проверить результат:
[Benchmark]
public int MultiArrayWithAsEnumerable()
{
return m_multiArray.AsEnumerable().Last();
}
И вот окончательный результат:
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 |
Здесь мы видим, что есть выделение в куче двух итераторов (одного для метода расширения и еще одного для Enumerable.Last<T>), но нет упаковок самих элементов.
При использовании необобщённого IEnumerable упаковки, конечно, не избежать.
Но компилятор умный, и в некоторых случаях может обойтись без IEnumerable. Важный случай — это если объект, по которому производится перечисление, обладает открытым методом GetEnumerator с подходящей сигнатурой. В этом случае будет использован именно он.*
Второй важный частный случай (и именно он у нас имеет место) — это массивы. Компилятор знает, как можно более эффективно обходить массивы, и иногда пользуется этим.
Например, вот такая функция
static int[,] array = ...;
static void Test()
{
foreach (var val in array)
Console.WriteLine(val);
}
скомпилировалась так, как будто она была написана следующим образом:
int[,] array = Program.array;
int upperBound = array.GetUpperBound(0);
int upperBound2 = array.GetUpperBound(1);
for (int i = array.GetLowerBound(0); i <= upperBound; i++)
{
for (int j = array.GetLowerBound(1); j <= upperBound2; j++)
{
Console.WriteLine(array[i, j]);
}
}
Для случая Cast<T>, кажется, оптимизатор не пытается улучшить код для массивов, и таки использует Cast. В коде Cast<T> есть проверка на наличие типизированного варианта IEnumerable<T> (и в этом случае упаковки бы не было), но массив его не поддерживает. Так что выполняется итерация по IEnumerable с упаковкой результатов. В последующих версиях языка, возможно, оптимизатор станет умнее (если разработчики сочтут этот случай важным).
(Для недоверчивых, вот IL-код:
// int[,] array = Program.array;
IL_0000: ldsfld int32[0..., 0...] Test.Program::'array'
IL_0005: stloc.0
// int upperBound = array.GetUpperBound(0);
IL_0006: ldloc.0
IL_0007: ldc.i4.0
IL_0008: callvirt instance int32 [mscorlib]System.Array::GetUpperBound(int32)
IL_000d: stloc.1
// int upperBound2 = array.GetUpperBound(1);
IL_000e: ldloc.0
IL_000f: ldc.i4.1
IL_0010: callvirt instance int32 [mscorlib]System.Array::GetUpperBound(int32)
IL_0015: stloc.2
// i = array.GetLowerBound(0)
IL_0016: ldloc.0
IL_0017: ldc.i4.0
IL_0018: callvirt instance int32 [mscorlib]System.Array::GetLowerBound(int32)
IL_001d: stloc.3
IL_001e: br.s IL_0048 // jump to outer loop check
// loop start (head: IL_0048)
// j = array.GetLowerBound(1)
IL_0020: ldloc.0
IL_0021: ldc.i4.1
IL_0022: callvirt instance int32 [mscorlib]System.Array::GetLowerBound(int32)
IL_0027: stloc.s 4
IL_0029: br.s IL_003f // jump to inner loop check
// loop start (head: IL_003f)
// array[i, j]
IL_002b: ldloc.0
IL_002c: ldloc.3
IL_002d: ldloc.s 4
IL_002f: call instance int32 int32[0..., 0...]::Get(int32, int32)
IL_0034: call void [mscorlib]System.Console::WriteLine(int32)
// j++
IL_0039: ldloc.s 4
IL_003b: ldc.i4.1
IL_003c: add
IL_003d: stloc.s 4
// j <= upperBound2
IL_003f: ldloc.s 4
IL_0041: ldloc.2
IL_0042: ble.s IL_002b
// end loop
// i++
IL_0044: ldloc.3
IL_0045: ldc.i4.1
IL_0046: add
IL_0047: stloc.3
// i <= upperBound
IL_0048: ldloc.3
IL_0049: ldloc.1
IL_004a: ble.s IL_0020
// end loop
IL_004c: ret
Проверяйте!)
*Ссылка на документацию:
GetEnumerator method:и только после этого
Это позволяет, в частности, при итерации по List<T> итерировать не по интерфейсу IEnumerator<T>, а по структуре List<T>.Enumerator, и тем самым избежать упаковки этой структуры.
Современные инструменты для криптотрейдинга: как технологии помогают принимать решения
Апостиль в Лос-Анджелесе без лишних нервов и бумажной волокиты
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости