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 -
foreacheliminará el iterador al final; eso es simple paraIEnumerator<T>que se extiende aIDisposable, pero comoIEnumeratorno 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
IEnumerableoIEnumerable<T>, siempre que tenga un métodoGetEnumerator()aplicable que devuelva un tipo con los miembrosCurrentyMoveNext()adecuados. Como se señaló en los comentarios, un tipo también puede implementarIEnumerableoIEnumerable<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
GetEnumeratorque devuelve algo que tiene un método públicoMoveNexty una propiedadCurrentpú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
IEnumerabledondeGetEnumeratordevuelveIEnumeratorque tiene un método públicoMoveNexty una propiedadCurrentpública. Pero un caso secundario interesante es que incluso si implementaIEnumerableexplícitamente (es decir, no hay un método públicoGetEnumeratoren la clase deTest), puede tener unforeach.class Test : IEnumerable { IEnumerator IEnumerable.GetEnumerator() { } }Esto se debe a que, en este caso,
foreachse implementa como (siempre que no haya otro método públicoGetEnumeratoren 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, elforeachse convierte a (siempre que no haya otro método públicoGetEnumeratoren 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
Enumeratordebe tener un método públicoMoveNexty una propiedadCurrentpública. En otras palabras, si está implementando la interfazIEnumerator, debe implementarse implícitamente. Por ejemplo,foreachno 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 deforeachindependientemente 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
foreachinvolucrado en la implementación deforeachdonde el elemento de recopilación se devuelve al tipo (especificado en el propio bucleforeach). Lo que significa que incluso si hubieras escrito elSomethingEnumeratorasí: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
Somethinges 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.