int[,] tabla = { {1,2}, {3,4} }; foreach (int elemento in tabla) Console.WriteLine(elemento);
|
Cuya salida por pantalla es:
En general, se considera que una colección es todo aquel objeto que implemente las interfaces IEnumerable o IEnumerator del espacio de nombres System.Collections de la BCL, que están definidas como sigue:
interface IEnumerable { [C#] IEnumerator GetEnumerator(); }
interface IEnumerator { [C#] object Current {get;} [C#] bool MoveNext(); [C#] void Reset(); }
|
El método Reset() ha de implementarse de modo que devuelva el enumerador reiniciado a un estado inicial donde aún no referencie ni siquiera al primer elemento de la colección sino que sea necesario llamar a MoveNext() para que lo haga.
El método MoveNext() se ha de implementar de modo que haga que el enumerador pase a apuntar al siguiente elemento de la colección y devuelva un booleano que indique si tras avanzar se ha alcanzado el final de la colección.
La propiedad Current se ha de implementar de modo que devuelva siempre el elemento de la colección al que el enumerador esté referenciando. Si se intenta leer Current habiéndose ya recorrido toda la colección o habiéndose reiniciado la colección y no habiéndose colocado en su primer elemento con MoveNext(), se ha de producir una excepción de tipo System.Exception.SystemException.InvalidOperationException
Otra forma de conseguir que foreach considere que un objeto es una colección válida consiste en hacer que dicho objeto siga el patrón de colección. Este patrón consiste en definir el tipo del objeto de modo que sus objetos cuenten con un método público GetEnumerator() que devuelva un objeto no nulo que cuente con una propiedad pública llamada Current que permita leer el elemento actual y con un método público bool MoveNext() que permita cambiar el elemento actual por el siguiente y devuelva false sólo cuando se haya llegado al final de la colección.
El siguiente ejemplo muestra ambos tipos de implementaciones:
using System; using System.Collections;
class Patron { private int actual = -1; public Patron GetEnumerator() { return this; } public int Current { get {return actual;} } public bool MoveNext() { bool resultado = true; actual++; if (actual==10) resultado = false; return resultado; } } class Interfaz:IEnumerable,IEnumerator { private int actual = -1; public object Current { get {return actual;} } public bool MoveNext() { bool resultado = true; actual++; if (actual==10) resultado = false; return resultado; } public IEnumerator GetEnumerator() { return this; } public void Reset() { actual = -1; } }
class Principal { public static void Main() { Patron obj = new Patron(); Interfaz obj2 = new Interfaz(); foreach (int elem in obj) Console.WriteLine(elem); foreach (int elem in obj2) Console.WriteLine(elem); } }
|
Nótese que en realidad en este ejemplo no haría falta implementar IEnumerable, puesto que la clase Interfaz ya implementa IEnumerator y ello es suficiente para que pueda ser recorrida mediante foreach.
La utilidad de implementar el patrón colección en lugar de la interfaz IEnumerable es que así no es necesario que Current devuelva siempre un object, sino que puede devolver objetos de tipos más concretos y gracias a ello puede detectarse al compilar si el <tipoElemento> indicado puede o no almacenar los objetos de la colección.
Por ejemplo, si en el ejemplo anterior sustituimos en el último foreach el <tipoElemento> indicado por Patrón, el código seguirá compilando pero al ejecutarlo saltará una excepción System.InvalidCastException. Sin embargo, si la sustitución se hubiese hecho en el penúltimo foreach, entonces el código directamente no compilaría y se nos informaría de un error debido a que los objetos int no son convertibles en objetos Patrón.
También hay que tener en cuenta que la comprobación de tipos que se realiza en tiempo de ejecución si el objeto sólo implementó la interfaz IEnumerable es muy estricta, en el sentido de que si en el ejemplo anterior sustituimos el <tipoElemento> del último foreach por byte también se lanzará la excepción al no ser los objetos de tipo int implícitamente convertibles en bytes sino sólo a través del operador () Sin embargo, cuando se sigue el patrón de colección las comprobaciones de tipo no son tan estrictas y entonces sí que sería válido sustituir int por byte en <tipoElemento>.
El problema de sólo implementar el patrón colección es que este es una característica propia de C# y con las instrucciones foreach (o equivalentes) de lenguajes que no lo soporten no se podría recorrer colecciones que sólo siguiesen este patrón. Una solución en estos casos puede ser hacer que el tipo del objeto colección implemente tanto la interfaz IEnumerable como el patrón colección. Obviamente esta interfaz debería implementarse explícitamente para evitarse conflictos derivados de que sus miembros tengan signaturas coincidentes con las de los miembros propios del patrón colección.
Si un objeto de un tipo colección implementa tanto la interfaz IEnumerable como el patrón de colección, entonces en C# foreach usará el patrón colección para recorrerlo.