I can't seem to reconcile these two observations:
T[,]
) can't be assigned to a variable of type IEnumerable<T>
foreach(var item in (T[,])array2d)
, the compiler knows that var
is T
.I thought foreach
was just syntactic sugar that uses IEnumerable<T>.GetEnumerator()
(in which case var
is T
), or IEnumerable.GetEnumerator()
(in which case var
is object
).
Here is the scenario that led me to this question. I have a method that takes an IEnumerable<Foo>
.
int Average(IEnumerable<Foo> values)
{
// lots of code
foreach (var foo in values)
{
var bar = foo.GetBar();
// lots of code
}
// lots of code
return result;
}
Foo[] array1d = /**/;
Average(array1d);
I then wanted to pass a 2D array to this method, but it doesn't compile; the error is CS1503 Argument 1: cannot convert from 'Foo[*,*]' to 'System.Collections.Generic.IEnumerable<Foo>'
.
Foo[,] array2d = /**/;
Average(array2d);
However, I discovered that it does compile if I just add this overload. When I mouse over var foo
, the compiler knows that it is a Foo
, and is able to find Foo.GetBar()
.
void Average(Foo[,] values)
{
// lots of code
foreach (var foo in values)
{
var bar = foo.GetBar();
// lots of code
}
// lots of code
return result;
}
Unfortunately, this duplicates my code. I could alternatively call Average(array2d.Cast<Foo>());
, but this adds an extra iterator, and I'm looking for performance. The iterator probably won't affect performance significantly, but it feels like there has to be a better way.
I thought foreach was just syntactic sugar that uses
IEnumerable<T>.GetEnumerator()
(in which casevar
isT
), orIEnumerable.GetEnumerator()
(in which casevar
isobject
).
That's not the case - the language specification has specific rules to handle arrays as well as IEnumerable<>
, basically. Back in C# 1, we didn't have generics at all, so for foreach
to work over arrays there had to be specific rules.
For much more detail, see the language specification - it's section 13.9.5 in the C# 7 spec for example.
In particular:
If the type
X
of expression is an array type then there is an implicit reference conversion fromX
to theIEnumerable
interface (sinceSystem.Array
implements this interface). The collection type is theIEnumerable
interface, the enumerator type is theIEnumerator
interface and the iteration type is the element type of the array typeX
.
It's also worth noting this:
An implementation is permitted to implement a given
foreach_statement
differently; e.g., for performance reasons, as long as the behavior is consistent with the above expansion.
If you look at the IL created by Roslyn (or older MS compilers) for a foreach
loop over an array, it doesn't call GetEnumerator()
at all - it's much more efficient to use the array op codes directly.