array - foreach c# list
¿Cómo se implementa foreach en C#? (2)
No usa una función anónima, no. Básicamente, el compilador convierte el código en algo ampliamente equivalente al bucle while que se muestra aquí.
foreach
no es una llamada de función, está incorporada en el propio lenguaje, al igual que for
bucles y los bucles while. No hay necesidad de devolver nada o "tomar" una función de ningún tipo.
Tenga en cuenta que foreach
tiene algunas arrugas interesantes:
- Cuando se recorre una matriz (conocida en tiempo de compilación), el compilador puede usar un contador de bucle y comparar con la longitud de la matriz en lugar de usar un
IEnumerator
-
foreach
eliminará el iterador al final; eso es simple paraIEnumerator<T>
que se extiende aIDisposable
, pero comoIEnumerator
no lo hace , el compilador inserta una verificación para probar en el momento de la ejecución si el iterador implementaIDisposable
- Puede iterar sobre los tipos que no implementan
IEnumerable
oIEnumerable<T>
, siempre que tenga un métodoGetEnumerator()
aplicable que devuelva un tipo con los miembrosCurrent
yMoveNext()
adecuados. Como se señaló en los comentarios, un tipo también puede implementarIEnumerable
oIEnumerable<T>
explícitamente, pero tiene unGetEnumerator()
públicoGetEnumerator()
que devuelve un tipo diferente aIEnumerator
/IEnumerator<T>
. Consulte laList<T>.GetEnumerator()
para ver un ejemplo: esto evita la creación innecesaria de un objeto de tipo de referencia en muchos casos.
Consulte la sección 8.8.4 de la especificación C # 4 para obtener más información.
Esta pregunta ya tiene una respuesta aquí:
- ¿Cómo funcionan los bucles foreach en C #? 7 respuestas
¿Cómo se implementa exactamente foreach
en C #?
Me imagino una parte de ella pareciendo:
var enumerator = TInput.GetEnumerator();
while(enumerator.MoveNext())
{
// do some stuff here
}
Sin embargo, no estoy seguro de lo que realmente está pasando. ¿Qué metodología se usa para devolver el enumerator.Current
? ¿ enumerator.Current
para cada ciclo? ¿Devuelve [para cada ciclo] o toma una función anónima o algo para ejecutar el cuerpo de foreach
?
Sorprendido la aplicación exacta no se toca. Si bien lo que ha publicado en la pregunta es el formulario más simple, la implementación completa (incluida la eliminación del enumerador, la conversión, etc.) se encuentra en la sección 8.8.4 de la especificación.
Ahora hay 2 escenarios donde se puede ejecutar un bucle foreach
en un tipo:
Si el tipo tiene un método público / no estático / no genérico / sin parámetros llamado
GetEnumerator
que devuelve algo que tiene un método públicoMoveNext
y una propiedadCurrent
pública. Como lo señaló el Sr. Eric Lippert en este artículo del blog , esto fue diseñado para adaptarse a la era pre genérica tanto para la seguridad de tipos como para los problemas de rendimiento relacionados con el boxeo en el caso de los tipos de valor. Tenga en cuenta que este es un caso de escribir pato . Por ejemplo, esto funciona:class Test { public SomethingEnumerator GetEnumerator() { } } class SomethingEnumerator { public Something Current //could return anything { get { return ... } } public bool MoveNext() { } } //now you can call foreach (Something thing in new Test()) //type safe { }
Esto es traducido por el compilador a:
E enumerator = (collection).GetEnumerator(); try { ElementType element; //pre C# 5 while (enumerator.MoveNext()) { ElementType element; //post C# 5 element = (ElementType)enumerator.Current; statement; } } finally { IDisposable disposable = enumerator as System.IDisposable; if (disposable != null) disposable.Dispose(); }
Si el tipo implementa
IEnumerable
dondeGetEnumerator
devuelveIEnumerator
que tiene un método públicoMoveNext
y una propiedadCurrent
pública. Pero un caso secundario interesante es que incluso si implementaIEnumerable
explícitamente (es decir, no hay un método públicoGetEnumerator
en la clase deTest
), puede tener unforeach
.class Test : IEnumerable { IEnumerator IEnumerable.GetEnumerator() { } }
Esto se debe a que, en este caso,
foreach
se implementa como (siempre que no haya otro método públicoGetEnumerator
en la clase):IEnumerator enumerator = ((IEnumerable)(collection)).GetEnumerator(); try { ElementType element; //pre C# 5 while (enumerator.MoveNext()) { ElementType element; //post C# 5 element = (ElementType)enumerator.Current; statement; } } finally { IDisposable disposable = enumerator as System.IDisposable; if (disposable != null) disposable.Dispose(); }
Si el tipo implementa
IEnumerable<T>
explícitamente, elforeach
se convierte a (siempre que no haya otro método públicoGetEnumerator
en la clase):IEnumerator<T> enumerator = ((IEnumerable<T>)(collection)).GetEnumerator(); try { ElementType element; //pre C# 5 while (enumerator.MoveNext()) { ElementType element; //post C# 5 element = (ElementType)enumerator.Current; //Current is `T` which is cast statement; } } finally { enumerator.Dispose(); //Enumerator<T> implements IDisposable }
Algunas cosas interesantes a tener en cuenta son:
En los dos casos anteriores, la clase
Enumerator
debe tener un método públicoMoveNext
y una propiedadCurrent
pública. En otras palabras, si está implementando la interfazIEnumerator
, debe implementarse implícitamente. Por ejemplo,foreach
no funcionará para este enumerador:public class MyEnumerator : IEnumerator { void IEnumerator.Reset() { throw new NotImplementedException(); } object IEnumerator.Current { get { throw new NotImplementedException(); } } bool IEnumerator.MoveNext() { throw new NotImplementedException(); } }
(Gracias a Roy Namir por señalar esto. Para cada implementación no es tan fácil como parece en la superficie)
Prioridad del enumerador: parece que si tiene un método
public GetEnumerator
, esa es la opción predeterminada deforeach
independientemente de quién lo esté implementando. Por ejemplo:class Test : IEnumerable<int> { public SomethingEnumerator GetEnumerator() { //this one is called } IEnumerator<int> IEnumerable<int>.GetEnumerator() { } }
Si no tiene una implementación pública (es decir, solo una implementación explícita), la prioridad es como
IEnumerator<T>
>>IEnumerator
.Hay un operador de
foreach
involucrado en la implementación deforeach
donde el elemento de recopilación se devuelve al tipo (especificado en el propio bucleforeach
). Lo que significa que incluso si hubieras escrito elSomethingEnumerator
así:class SomethingEnumerator { public object Current //returns object this time { get { return ... } } public bool MoveNext() { } }
Podrías escribir:
foreach (Something thing in new Test()) { }
Debido a que
Something
es compatible con el tipo, siguiendo las reglas de C #, o en otras palabras, el compilador lo permite si hay una conversión explícita posible entre los dos tipos. De lo contrario el compilador lo impide. La conversión real se realiza en tiempo de ejecución que puede o no puede fallar.