4

For the reasons that I still do not understand (see this SO question) multidimensional arrays in CLR do not implement IEnumerable<T>. So the following does not compile:

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

Then how come that this works just fine in 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:

The following code works because the C# compiler replaces the foreach with for loops to go through each dimension.

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

becomes

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);
    }
}
Community
  • 1
  • 1
Prankster
  • 4,031
  • 5
  • 33
  • 44
  • 1
    foreach(...) works because [,] implements IEnumerable (non generic one) while the query wants IEnumerable. – Samuel Apr 06 '09 at 16:38
  • What's interesting is that Visual Studio shows type of e as int, and not like object that you would expect in the case of IEnumerable – Prankster Apr 06 '09 at 16:45
  • Also, I want to note that the question is "Why it works in VB ?", not why it is not implemented in C# – Prankster Apr 06 '09 at 16:48
  • You get 0.5 points for using reflector, but not the other half for jumping to the wrong conclusion. – Samuel Apr 06 '09 at 17:41
  • @Samuel. And my wrong conclusion is? If foreach was using IEnumerable, all it could get was object, not int. – Prankster Apr 06 '09 at 18:12
  • @Samuel. And how about answering THE question? – Prankster Apr 06 '09 at 18:13
  • 1
    Joel has answered your question. [*,*] does not implement IEnumerable, but it does implement IEnumerable. You can call GetEnumerator() yourself and pretend to be a foreach loop. – Samuel Apr 06 '09 at 18:19
  • You didn't read the question. – Prankster Apr 06 '09 at 18:21
  • 1
    Yes I did, it works in VB.Net because they took slightly different approaches to LINQ querying and it favours holding the hand of the programmer more and calls IEnumerable.Cast() behind the scenes. – Samuel Apr 06 '09 at 18:34

4 Answers4

8

The query works in VB.Net because it gets transformed into

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

This works because you can call Cast<TResult>() on IEnumerable, which [*,*] implements.

The LINQ query doesn't work in C# because of the different approach the C# and VB.Net designers took. VB.Net takes a more hand holding approach and fixes your mistake and converts IEnumerable to IEnumerable<object> so it can be used.

In C#, you can simulate this by using

var q = from e in m.Cast<object>() select e;
Samuel
  • 37,778
  • 11
  • 85
  • 87
3

There are two reasons they don't implement it natively in C#:

  • There's more than one way you could do it. Do you want each 'cell', or do you want each 'row'? And how do you define 'row': [], IEnumerable, other? What if there are more than two dimensions? As soon as they pick one way, an army of developers will tell them they should have done it a different way.
  • Thanks to iterator blocks and the yield keyword, it just so easy to implement your own that's specific to your need at the time. Of course, that's a C# construct, but it's not that much harder in VB.
Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • But as you can see from the question, VB.NET doesn't require it. – Prankster Apr 06 '09 at 15:11
  • 1
    @prankster: That's probably because of VB's roots. VB.Net carried over a lot of the hand holding programming aspects. This assumption of how you would want IEnumerable implemented would be one of them. – Samuel Apr 06 '09 at 15:35
  • ... added "in C#" to the end of the first sentence after prankster's comment. – Joel Coehoorn Apr 06 '09 at 15:36
2

The MSDN page for Array includes this:

Important Note: In the .NET Framework version 2.0, the Array class implements the System.Collections.Generic.IList<T>, System.Collections.Generic.ICollection<T>, and System.Collections.Generic.IEnumerable<T> generic interfaces. The implementations are provided to arrays at run time,

Note the final words in the quote... it appears this generation does not happen for multi-dimensional arrays (so a documentation bug).

But as others have noted, what would T be? A good case can be made for T[] (or, these days with LINQ, IEnumerable<T>).

In the end, if you want to iterate all the array's members just stick with IEnumerable and Cast<T> extension. Otherwise easy to write your own.

Richard
  • 106,783
  • 21
  • 203
  • 265
2

Tip: instead of Cast<object>() use a typed range variable


Samuel stated:

In C#, you can simulate this by using

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

which is of course correct as far as mimicking VB in C# is concerned, but you would loose your type information. Instead, it is much easier and as it turns out, slightly better readable, to simply declare your range variable.

The following compiles, performs better, is type safe and does not loose type information:

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>

In the original suggestion, you would have an IEnumerable<object>, using int e you change that into IEnumerable<int>, which has its advantages.

Abel
  • 56,041
  • 24
  • 146
  • 247