Nel suo post di ieri, "foreach l'insidioso", Luca si e' chiesto come mai il seguente snippet:
using System.Collections.Generic;
interface IPersistent { }
class Invoice : IPersistent { }
class Order : IPersistent { }
class Program {
static void Main() {
List<IPersistent> changedDocuments = new List<IPersistent>();
changedDocuments.Add(new Invoice());
changedDocuments.Add(new Order());
foreach (Invoice changedInvoice in changedDocuments) { }
}
}
compili. Secondo me, il comportamento del compilatore e' giusto, voluto e documentato. Le specifiche del linguaggio (15.8.4, ECMA-334), dicono:
"A foreach statement of the form
foreach(V v in x) embedded-statement
is then expanded to:
{
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}
The variable e is not visible to or accessible to the expression x or the embedded statement or any other source code of the program. The variable v is read-only in the embedded statement. If there is not an explicit conversion from T (the element type) to V (the type in the foreach statement), an error is produced and no further steps are taken."
Quindi e' la responsabilita' del programmatore quella di assicurarsi che il cast sia valido. Potremmo volere per esempio fare un foreach su tutte le Label nella ControlCollection dei Controls di una Form - sara' il nostro compito quello di "filtrare" gli elementi di tipo Label e fare il foreach soltanto su questi, oppure assicurarsi che nella Controls ci sono solo Label. Perche' il compilatore non ci aiuta? Probabilmente perche' la lista potrebbe essere costruita anche altrove!
In ogni caso, se cerchiamo un aiuto da parte dell'intellisense e del compilatore, possiamo sostituire il foreach nello snippet di Luca, con:
changedDocuments.ForEach(delegate(IPersistent persistent) { });
Sorprendemente, otteniamo anche un codice IL piu' piccolo rispetto alla variante con foreach. Il type parameter dell'argomento action del metodo ForEach, e' vincolato ora ad essere identico al type parameter della nostra List, cioe' IPersistent.