sintaxis - ¿Cuándo debería usar IEnumerator para el bucle en c#?
foreach c# explicacion (4)
De acuerdo con la especificación del lenguaje C # :
Una declaración foreach del formulario
foreach (V v in x) embedded-statement
luego se expande a:
{
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
}
finally {
... // Dispose e
}
}
La esencia de tus dos ejemplos es la misma, pero hay algunas diferencias importantes, sobre todo el try
/ finally
.
Me preguntaba si hay ocasiones en las que sea ventajoso usar un IEnumerator en un bucle foreach para iterar a través de una colección. Por ejemplo, ¿hay algún momento en el que sea mejor usar cualquiera de los siguientes ejemplos de código sobre el otro?
IEnumerator<MyClass> classesEnum = myClasses.GetEnumerator();
while(classesEnum.MoveNext())
Console.WriteLine(classesEnum.Current);
en lugar de
foreach (var class in myClasses)
Console.WriteLine(class);
En C #, foreach
está haciendo exactamente lo mismo que el código de IEnumerator
que publicó anteriormente. La construcción foreach
se proporciona para que sea más fácil para el programador. Creo que el IL generado en ambos casos es el mismo / similar.
Lo usé a veces cuando la primera iteración hace algo diferente de los demás.
Por ejemplo, si desea imprimir miembros a la consola y separar resultados por línea, puede escribir.
using (IEnumerator<MyClass> classesEnum = myClasses.GetEnumerator()) {
if (classEnum.MoveNext())
Console.WriteLine(classesEnum.Current);
while (classesEnum.MoveNext()) {
Console.WriteLine("-----------------");
Console.WriteLine(classesEnum.Current);
}
}
Entonces el resultado es
myClass 1
-----------------
myClass 2
-----------------
myClass 3
Y otra situación es cuando itero 2 enumeradores juntos.
using (IEnumerator<MyClass> classesEnum = myClasses.GetEnumerator()) {
using (IEnumerator<MyClass> classesEnum2 = myClasses2.GetEnumerator()) {
while (classesEnum.MoveNext() && classEnum2.MoveNext())
Console.WriteLine("{0}, {1}", classesEnum.Current, classesEnum2.Current);
}
}
resultado
myClass 1, myClass A
myClass 2, myClass B
myClass 3, myClass C
Usar el enumerador le permite hacer más flexible en su iteración. Pero en la mayoría de los casos, puedes usar foreach
Primero, tenga en cuenta que una gran diferencia en su ejemplo (entre foreach
y GetEnumerator
) es que foreach
garantiza llamar a Dispose()
en el iterador si el iterador es IDisposable
. Esto es importante para muchos iteradores (que podrían estar consumiendo un feed de datos externo, por ejemplo).
De hecho, hay casos en los que foreach
no es tan útil como nos gustaría.
Primero, está el caso del "primer ítem" discutido aquí (foreach / detección de la primera iteración) .
Pero más; Si intenta escribir el método Zip
falta para unir dos secuencias enumerables (o el método SequenceEqual
), encontrará que puede usar foreach
en ambas secuencias, ya que eso haría una combinación cruzada. Necesita usar el iterador directamente para uno de ellos:
static IEnumerable<T> Zip<T>(this IEnumerable<T> left,
IEnumerable<T> right)
{
using (var iter = right.GetEnumerator())
{
// consume everything in the first sequence
foreach (var item in left)
{
yield return item;
// and add an item from the second sequnce each time (if we can)
if (iter.MoveNext())
{
yield return iter.Current;
}
}
// any remaining items in the second sequence
while (iter.MoveNext())
{
yield return iter.Current;
}
}
}
static bool SequenceEqual<T>(this IEnumerable<T> left,
IEnumerable<T> right)
{
var comparer = EqualityComparer<T>.Default;
using (var iter = right.GetEnumerator())
{
foreach (var item in left)
{
if (!iter.MoveNext()) return false; // first is longer
if (!comparer.Equals(item, iter.Current))
return false; // item different
}
if (iter.MoveNext()) return false; // second is longer
}
return true; // same length, all equal
}