Многомерные массивы не реализуют IEnumerable <T>, или они?

По причинам, которые я до сих пор не понимаю (см. этот вопрос SO), многомерные массивы в CLR не реализуют IEnumerable<T>. Итак, следующее не компилируется:

var m = new int[2,2] {{1, 2}, {3, 4}};
var q = from e in m select e;

Тогда как получилось, что этот отлично работает в VB.NET?

Sub Main()
    Dim m(,) As Integer = {{1, 2}, {3, 4}}
    Dim q = From e In m Select e

    For Each i In q
        Console.WriteLine(i)
    Next
End Sub

Update:

Следующий код работает, потому что компилятор С# заменяет циклы foreach на for для прохождения каждого измерения.

foreach(var e in m)
    Console.WriteLine(e);

становится

int[,] numArray3 = new int[,] { { 2, 2 }, { 3, 3 } };
int upperBound = numArray3.GetUpperBound(0);
int num4 = numArray3.GetUpperBound(1);
for (int i = numArray3.GetLowerBound(0); i <= upperBound; i++)
{
    for (int j = numArray3.GetLowerBound(1); j <= num4; j++)
    {
        int num = numArray3[i, j];
        Console.WriteLine(num);
    }
}

Ответ 1

Запрос работает в VB.Net, потому что он преобразуется в

IEnumerable<object> q = m.Cast<object>().Select<object, object>(o => o);

Это работает, потому что вы можете вызвать Cast<TResult>() на IEnumerable, который реализует [*,*].

Запрос LINQ не работает на С# из-за другого подхода, который взяли дизайнеры С# и VB.Net. VB.Net использует более удобный подход и исправляет вашу ошибку и преобразует IEnumerable в IEnumerable<object>, чтобы его можно было использовать.

В С# вы можете имитировать это, используя

var q = from e in m.Cast<object>() select e;

Ответ 2

Есть две причины, по которым они не реализуют его в С#:

  • Там вы можете сделать несколько способов. Вы хотите, чтобы каждая "ячейка", или вам нужна каждая "строка"? И как вы определяете "row": [], IEnumerable, другое? Что делать, если есть более двух измерений? Как только они выберут один путь, армия разработчиков скажет им, что они должны были сделать это по-другому.
  • Благодаря блокам итератора и ключевому слову yield, это просто так легко реализовать в соответствии с вашими потребностями в то время. Конечно, это С# -конструкция, но это не намного сложнее в VB.

Ответ 3

Страница MSDN для Array включает в себя следующее:

Важное примечание:В .NET Framework версии 2.0 класс Array реализует универсальные интерфейсы System.Collections.Generic.IList <T> , System.Collections.Generic.ICollection <T> и System.Collections.Generic.IEnumerable <T> . Реализации предоставляются массивам во время выполнения,

Обратите внимание на последние слова в цитате... похоже, что это поколение не происходит для многомерных массивов (так что ошибка документации).

Но, как отметили другие, что бы было T быть? Хороший пример можно сделать для T[] (или в наши дни с LINQ, IEnumerable<T>).

В конце концов, если вы хотите итерации всех членов массива, просто придерживайтесь расширения IEnumerable и Cast <T> . В противном случае легко написать свой собственный.

Ответ 4

Совет: вместо Cast <object> () используйте типизированную переменную диапазона


Самуил заявил:

В С# вы можете имитировать это, используя

var q = from e in m.Cast<object>() select e;
// q is of type IEnumerable<object>

который, конечно, правильный, если имитировать VB в С#, но вы потеряете информацию о типе. Вместо этого, это намного проще и, как оказалось, немного лучше читаемо, просто объявить переменную диапазона.

Следующие компиляции, выполняемые лучше, безопасны по типу и не теряют информацию о типе:

var m = new int[2, 2] { { 1, 2 }, { 3, 4 } };
var q = from int e in m select e;
// q is of type IEnumerable<int>

В исходном предложении у вас будет IEnumerable<object>, используя int e, вы измените это на IEnumerable<int>, у которого есть свои преимущества.