foreach and type safetyforeach is probably the most common language construct for iteration in C# and
because of this is it worth knowning that there is a serious problem in using it.
When used with enumerations over reference types foreach
(just like arrays) lacks any notation of type safety.
According to the C# 2.0 standard (ECMA-334 C# Language Specification under “15.8.4 The foreach statement” p.238):
[…] a collection type C, enumerator type E and elementtype T. A foreach statement of the form
foreach (V v in x) embedded-statementis then expanded to:
{ E e = ((C)(x)).GetEnumerator(); try { V v; while (e.MoveNext()) { v = (V)(T)e.Current; embedded-statement } } finally { ... // Dispose e } }
Exactly what types that is actually expanded as C, E and T
and which code "Dispose e" represents is a result of a fairly advanced
(or at least complicated - think of a large chunk of nested ifs) algorithm of cases based on whether
x implements IEnumerable, IEnumerable<T>,
is an array type and so on. (The rules are described in detail in
ECMA-334.)
For programmers interested in type safety and fundamental design the generic
IEnumerable interface is probably the most common type to use
together with foreach:
IEnumerable<Animal> myCollection = ...
foreach(Animal myElement in myCollection)
doSometing(myElement);
Which the compiler will expanded to:
IEnumerable<Animal> myCollection = ...
{
IEnumerator<Animal> e = ((IEnumerable<Animal>)(myCollection)).GetEnumerator();
try {
Animal myElement;
while (e.MoveNext()) {
myElement = (Animal)(Animal)e.Current;
doSometing(myElement);
}
}
finally {
System.IDisposable d = e as System.IDisposable;
if (d != null) d.Dispose();
}
}
This expansion is not in itself wrong even though there are a few casts in there that is not needed.
It get worse if we, by mistake, write a subtype in place of Animal as the element
type:
IEnumerable<Animal> myCollection = ...
foreach(Giraffe myElement in myCollection)
doSometing(myElement);
Since the compiler accept upcasting of an Animal to a Giraffe it
will expand this without any complaint to:
IEnumerable<Animal> myCollection = ...
{
IEnumerator<Animal> e = ((IEnumerable<Animal>)(myCollection)).GetEnumerator();
try {
Giraffe myElement;
while (e.MoveNext()) {
myElement = (Giraffe)(Animal)e.Current; // <-- Note!
doSometing(myElement);
}
}
finally {
System.IDisposable d = e as System.IDisposable;
if (d != null) d.Dispose();
}
}
We will then get a InvalidCastException (from Animal to
Giraffe) at runtime!
To be such a central construct in the language as foreach is I think it is
an unbelievable serious flaw to open up for type errors like this.
Generic lists (List<T>) has a ForEach-method which does the
same thing as foreach with the difference of being type safe. This method could have
been moved to be a part of IEnumerable<T> when LINQ where introduced and the
problem would have been solved but this was not done.
The only sollution left if the write the code yourself and do your best to forget that
foreach ever existed:
public static void ForEach<T>(this IEnumerable<T> sequence, Action<T> action) {
IEnumerator<T> e = sequence.GetEnumerator();
try {
while (e.MoveNext())
action(e.Current);
}
finally {
System.IDisposable d = e as System.IDisposable;
if (d != null) d.Dispose();
}
}