¿Alguien puede explicarme IEnumerable e IEnumerator?

¿Alguien puede explicarme IEnumerable e IEnumerator?

por ejemplo, ¿cuándo usarlo sobre foreach? ¿Cuál es la diferencia entre IEnumerable e IEnumerator? ¿Por qué tenemos que usarlo?

por ejemplo, ¿cuándo usarlo sobre foreach?

No usa IEnumerable “over” foreach . La implementación de IEnumerable hace posible el uso de foreach .

Cuando escribes código como:

 foreach (Foo bar in baz) { ... } 

es funcionalmente equivalente a escribir:

 IEnumerator bat = baz.GetEnumerator(); while (bat.MoveNext()) { bar = (Foo)bat.Current ... } 

Por “funcionalmente equivalente”, quiero decir que en realidad es en lo que el comstackdor convierte el código. No puede usar foreach en baz en este ejemplo a menos que baz implemente IEnumerable .

IEnumerable significa que baz implementa el método

 IEnumerator GetEnumerator() 

El objeto IEnumerator que devuelve este método debe implementar los métodos

 bool MoveNext() 

y

 Object Current() 

El primer método avanza al siguiente objeto en el objeto IEnumerable que creó el enumerador, devolviendo false si está hecho, y el segundo devuelve el objeto actual.

Cualquier cosa en .Net que pueda iterar implementa IEnumerable . Si está construyendo su propia clase, y aún no hereda de una clase que implementa IEnumerable , puede hacer que su clase se pueda utilizar en las instrucciones foreach implementando IEnumerable (y creando una clase de enumerador que devolverá su nuevo método GetEnumerator ) .

Las interfaces IEnumerable e IEnumerator

Para comenzar a examinar el proceso de implementación de interfaces .NET existentes, veamos primero el rol de IEnumerable e IEnumerator. Recuerde que C # admite una palabra clave llamada foreach que le permite iterar sobre los contenidos de cualquier tipo de matriz:

 // Iterate over an array of items. int[] myArrayOfInts = {10, 20, 30, 40}; foreach(int i in myArrayOfInts) { Console.WriteLine(i); } 

Si bien podría parecer que solo los tipos de matriz pueden hacer uso de este constructo, la verdad del asunto es que cualquier tipo que respalde un método llamado GetEnumerator () puede ser evaluado por el constructo foreach. Para ilustrar, sígueme.

Supongamos que tenemos una clase de Garage:

 // Garage contains a set of Car objects. public class Garage { private Car[] carArray = new Car[4]; // Fill with some Car objects upon startup. public Garage() { carArray[0] = new Car("Rusty", 30); carArray[1] = new Car("Clunker", 55); carArray[2] = new Car("Zippy", 30); carArray[3] = new Car("Fred", 30); } } 

Idealmente, sería conveniente iterar sobre los subelementos del objeto Garage utilizando el constructo foreach, al igual que una matriz de valores de datos:

 // This seems reasonable ... public class Program { static void Main(string[] args) { Console.WriteLine("***** Fun with IEnumerable / IEnumerator *****\n"); Garage carLot = new Garage(); // Hand over each car in the collection? foreach (Car c in carLot) { Console.WriteLine("{0} is going {1} MPH", c.PetName, c.CurrentSpeed); } Console.ReadLine(); } } 

Lamentablemente, el comstackdor le informa que la clase Garage no implementa un método llamado GetEnumerator (). Este método está formalizado por la interfaz IEnumerable, que se encuentra al acecho dentro del espacio de nombres System.Collections. Las clases o estructuras que admiten este comportamiento anuncian que pueden exponer subtemas contenidos a la persona que llama (en este ejemplo, la palabra clave foreach en sí misma). Aquí está la definición de esta interfaz .NET estándar:

 // This interface informs the caller // that the object's subitems can be enumerated. public interface IEnumerable { IEnumerator GetEnumerator(); } 

Como puede ver, el método GetEnumerator () devuelve una referencia a otra interfaz llamada System.Collections.IEnumerator. Esta interfaz proporciona la infraestructura para permitir que la persona que llama atraviese los objetos internos contenidos en el contenedor compatible con IEnumerable:

 // This interface allows the caller to // obtain a container's subitems. public interface IEnumerator { bool MoveNext (); // Advance the internal position of the cursor. object Current { get;} // Get the current item (read-only property). void Reset (); // Reset the cursor before the first member. } 

Si desea actualizar el tipo de Garage para admitir estas interfaces, puede tomar el camino largo e implementar cada método manualmente. Si bien es cierto que puede proporcionar versiones personalizadas de GetEnumerator (), MoveNext (), Current y Reset (), existe una manera más sencilla. Como el tipo System.Array (así como muchas otras clases de colección) ya implementa IEnumerable e IEnumerator, puede simplemente delegar la solicitud al System.Array de la siguiente manera:

 using System.Collections; ... public class Garage : IEnumerable { // System.Array already implements IEnumerator! private Car[] carArray = new Car[4]; public Garage() { carArray[0] = new Car("FeeFee", 200); carArray[1] = new Car("Clunker", 90); carArray[2] = new Car("Zippy", 30); carArray[3] = new Car("Fred", 30); } public IEnumerator GetEnumerator() { // Return the array object's IEnumerator. return carArray.GetEnumerator(); } } 

Después de haber actualizado su tipo de garaje, puede usar el tipo de forma segura dentro de la construcción de C # foreach. Además, dado que el método GetEnumerator () se ha definido públicamente, el usuario del objeto también podría interactuar con el tipo IEnumerator:

 // Manually work with IEnumerator. IEnumerator i = carLot.GetEnumerator(); i.MoveNext(); Car myCar = (Car)i.Current; Console.WriteLine("{0} is going {1} MPH", myCar.PetName, myCar.CurrentSpeed); 

Sin embargo, si prefiere ocultar la funcionalidad de IEnumerable desde el nivel del objeto, simplemente haga uso de la implementación explícita de la interfaz:

 IEnumerator IEnumerable.GetEnumerator() { // Return the array object's IEnumerator. return carArray.GetEnumerator(); } 

Al hacerlo, el usuario ocasional del objeto no encontrará el método GetEnumerator () del Garaje, mientras que el constructo foreach obtendrá la interfaz en el fondo cuando sea necesario.

Adaptado de Pro C # 5.0 y .NET 4.5 Framework

La implementación de IEnumerable significa que su clase devuelve un objeto IEnumerator:

 public class People : IEnumerable { IEnumerator IEnumerable.GetEnumerator() { // return a PeopleEnumerator } } 

Implementar IEnumerator significa que su clase devuelve los métodos y propiedades para la iteración:

 public class PeopleEnumerator : IEnumerator { public void Reset()... public bool MoveNext()... public object Current... } 

Esa es la diferencia de todos modos.

Explicación a través de Analogy + Tutorial de código

Primero una explicación sin código, luego la agregaré más tarde.

Supongamos que dirige una compañía aérea. Y en cada avión quieres saber información sobre los pasajeros que vuelan en el avión. Básicamente, quieres poder “atravesar” el avión. En otras palabras, desea poder comenzar en el asiento delantero y luego abrirse paso hacia la parte trasera del avión, pidiéndoles información a los pasajeros: quiénes son, de dónde son, etc. Un avión solo puede hacer esto. , si esto es:

  1. contable, y
  2. si tiene un contador.

Si no hay ‘contador’, y si no es ‘contable’ no llegarás muy lejos. ¿Por qué estos requisitos? Porque eso es lo que requiere la interfaz.

¿Qué significa contable?

Si una línea aérea es “contable”, esto significa que DEBE haber un asistente de vuelo presente en el avión, cuyo único trabajo es contar, y este auxiliar de vuelo DEBE contar de una manera muy específica (de acuerdo con las Leyes de Aviación Civil):

  1. El mostrador / auxiliar de vuelo DEBE comenzar antes que el primer pasajero (en la parte delantera de todos donde demuestren seguridad, cómo ponerse el chaleco salvavidas, etc.).
  2. Él / ella (es decir, la azafata) DEBE “moverse al siguiente” por el pasillo hasta el primer asiento.
  3. Él / ella debe registrar: (i) quién es la persona en el asiento, y (ii) su ubicación actual en el pasillo.

Procedimientos de conteo

El capitán de la aerolínea quiere un informe sobre cada pasajero a medida que se investigan o cuentan. Entonces, después de hablar con la persona en el primer asiento, el auxiliar de vuelo / mostrador informa al capitán, y cuando se entrega el informe, el mostrador recuerda su posición exacta en el pasillo y continúa contando justo donde se fue apagado.

De esta manera, el capitán siempre puede tener información sobre la persona que está siendo investigada. De esta forma, si descubre que a este individuo le gusta el Manchester City, entonces puede darle a ese pasajero un trato preferencial, etc.

  • El contador continúa hasta llegar al final del avión.

Vamos a atar esto con los IEnumerables

  • Un enumerable es solo una colección de pasajeros en un avión. La Ley de Aviación Civil: estas son básicamente las reglas que deben cumplir todos los IEnumerables. Cada vez que el operador de la línea aérea va con el capitán con la información del pasajero, básicamente estamos ‘rindiendo’ el pasajero al capitán. El capitán básicamente puede hacer lo que quiera con el pasajero, excepto reorganizar a los pasajeros en el avión. En este caso, reciben un trato preferencial si siguen al Manchester City (¡uf!)

     foreach (Passenger passenger in Plane) // the airline hostess is now at the front of the plane // and slowly making her way towards the back // when she get to a particular passenger she gets some information // about the passenger and then immediately heads to the cabin // to let the captain decide what to do with it { // <---------- Note the curly bracket that is here. // we are now cockpit of the plane with the captain. // the captain wants to give the passenger free // champaign if they support manchester city if (passenger.supports_mancestercity()) { passenger.getFreeChampaign(); } else { // you get nothing! GOOD DAY SIR! } } // <---- Note the curly bracket that is here! the hostess has delivered the information to the captain and goes to the next person on the plane (if she has not reached the end of the plane) 

Resumen

En otras palabras, algo es contable si tiene un contador . Y el contador debe (básicamente): (i) recordar su lugar ( estado ), (ii) ser capaz de moverse a continuación , (iii) y conocer a la persona actual con la que está tratando.

Enumerable es simplemente una palabra elegante para "contable". En otras palabras, un enumerable le permite 'enumerar' (es decir, contar).

IEnumerable implementa GetEnumerator. Cuando se llama, ese método devolverá un IEnumerator que implementa MoveNext, Reset y Current.

Por lo tanto, cuando su clase implementa IEnumerable, está diciendo que puede llamar a un método (GetEnumerator) y obtener un nuevo objeto devuelto (un IEnumerator) que puede usar en un bucle como foreach.

La implementación de IEnumerable le permite obtener un IEnumerator para una lista.

IEnumerator permite el acceso secuencial de estilo foreach a los elementos en la lista, usando la palabra clave yield.

Antes de la implementación de foreach (en Java 1.4, por ejemplo), la forma de iterar en una lista era obtener un enumerador de la lista y luego preguntarle por el “siguiente” elemento de la lista, siempre que el valor se devolviera como el siguiente el artículo no es nulo Foreach simplemente lo hace implícitamente como una función de idioma, de la misma manera que lock () implementa la clase Monitor detrás de las escenas.

Espero que foreach funcione en las listas porque implementan IEnumerable.

  • Un objeto que implementa IEnumerable permite que otros visiten cada uno de sus elementos (por un enumerador) .
  • Un objeto que implementa IEnumerator es el que realiza la iteración. Está girando sobre un objeto enumerable.

Piensa en objetos enumerables como listas, montones, árboles.

IEnumerable e IEnumerator (y sus equivalentes generics IEnumerable e IEnumerator ) son interfaces base de implementaciones de iteradores en las colecciones de .Net Framework Class Libray .

IEnumerable es la interfaz más común que vería en la mayoría del código que hay. Permite el bucle foreach, los generadores (piense en el rendimiento ) y, debido a su pequeña interfaz, se usa para crear abstracciones estrechas. IEnumerable depende de IEnumerator .

IEnumerator , por otro lado, proporciona una interfaz de iteración de nivel ligeramente inferior. Se conoce como el iterador explícito que le da al progtwigdor más control sobre el ciclo de iteración.

IEnumerable

IEnumerable es una interfaz estándar que permite iterar sobre las colecciones que lo soportan (de hecho, todos los tipos de colección en los que puedo pensar hoy implementan IEnumerable ). El soporte del comstackdor permite funciones de lenguaje como foreach . En términos generales, habilita esta implementación de iterador implícito .

foreach Loop

 foreach (var value in list) Console.WriteLine(value); 

Creo que foreach loop es una de las razones principales para usar interfaces IEnumerable . foreach tiene una syntax muy sucinta y es muy fácil de entender en comparación con el estilo C clásico para bucles en los que necesita verificar las diversas variables para ver qué estaba haciendo.

rendimiento Palabra clave

Probablemente, una característica menos conocida es que IEnumerable también habilita generadores en C # con el uso de yield return de yield return y yield break .

 IEnumerable GetThings() { if (isNotReady) yield break; while (thereIsMore) yield return GetOneMoreThing(); } 

Abstracciones

Otro escenario común en la práctica es el uso de IEnumerable para proporcionar abstracciones minimalistas. Debido a que es una interfaz minúscula y de solo lectura, se recomienda exponer sus colecciones como IEnumerable (en lugar de List, por ejemplo). De esta forma, puedes cambiar tu implementación sin romper el código de tu cliente (por ejemplo, cambiar la Lista a una Lista Vinculada ).

Gotcha

Un comportamiento a tener en cuenta es que en las implementaciones de transmisión (por ejemplo, recuperar datos fila por fila de una base de datos, en lugar de cargar todos los resultados en la memoria primero) no puede iterar sobre la colección más de una vez. Esto está en contraste con las colecciones en memoria como List , donde puede iterar varias veces sin problemas. ReSharper, por ejemplo, tiene una inspección de código para la enumeración múltiple posible de IEnumerable .

IEnumerator

IEnumerator, por otro lado, es la interfaz detrás de escena que hace que IEnumerble-foreach-magic funcione. Estrictamente hablando, habilita iteradores explícitos.

 var iter = list.GetEnumerator(); while (iter.MoveNext()) Console.WriteLine(iter.Current); 

En mi experiencia, IEnumerator rara vez se utiliza en escenarios comunes debido a su syntax más detallada y semántica ligeramente confusa (al menos para mí; por ejemplo, MoveNext () también devuelve un valor, que el nombre no sugiere en absoluto).

Caso de uso para IEnumerator

Solo utilicé IEnumerator en bibliotecas y frameworks particulares (un nivel ligeramente inferior) donde estaba proporcionando interfaces IEnumerable . Un ejemplo es una biblioteca de procesamiento de flujo de datos que proporcionó una serie de objetos en un bucle foreach aunque los datos de detrás de escena se recostackron utilizando varias secuencias de archivos y serializaciones.

Codigo del cliente

 foreach(var item in feed.GetItems()) Console.WriteLine(item); 

Biblioteca

 IEnumerable GetItems() { return new FeedIterator(_fileNames) } class FeedIterator: IEnumerable { IEnumerator GetEnumerator() { return new FeedExplicitIterator(_stream); } } class FeedExplicitIterator: IEnumerator { DataItem _current; bool MoveNext() { _current = ReadMoreFromStream(); return _current != null; } DataItem Current() { return _current; } } 

Implementar IEnumerable significa esencialmente que el objeto puede repetirse. Esto no necesariamente significa que es una matriz, ya que hay ciertas listas que no se pueden indexar, pero puede enumerarlas.

IEnumerator es el objeto real utilizado para realizar las iteraciones. Controla el movimiento de un objeto al siguiente en la lista.

La mayoría de las veces, IEnumerable e IEnumerator se usan de forma transparente como parte de un bucle foreach .

Diferencias entre IEnumerable e IEnumerator:

  • IEnumerable usa IEnumerator internamente.
  • IEnumerable no sabe qué elemento / objeto se está ejecutando.
  • Cada vez que pasamos IEnumerator a otra función, conoce la posición actual del elemento / objeto.
  • Siempre que pasemos la colección IEnumerable a otra función, no conoce la posición actual del elemento / objeto (no sabe qué elemento está ejecutando)

    IEnumerable tiene un método GetEnumerator ()

 public interface IEnumerable : IEnumerable { IEnumerator GetEnumerator(); } 

IEnumerator tiene una propiedad actual y dos métodos Reset y MoveNext (que es útil para conocer la posición actual de un elemento en una lista).

 public interface IEnumerator { object Current { get; } bool MoveNext(); void Reset(); } 

Una comprensión del patrón Iterator será útil para usted. Recomiendo leer lo mismo.

Patrón de iterador

En un nivel alto, el patrón de iterador se puede usar para proporcionar una forma estándar de iterar a través de colecciones de cualquier tipo. Tenemos 3 participantes en el patrón de iterador, la colección real (cliente), el agregador y el iterador. El agregado es una clase de interfaz / resumen que tiene un método que devuelve un iterador. Iterator es una clase de interfaz / resumen que tiene métodos que nos permiten iterar a través de una colección.

Para implementar el patrón primero necesitamos implementar un iterador para producir un concreto que pueda iterar sobre la colección en cuestión (cliente) Luego la colección (cliente) implementa el agregador para devolver una instancia del iterador anterior.

Aquí está el diagtwig de UML Patrón de iterador

Así que básicamente en c #, IEnumerable es el agregado abstracto e IEnumerator es el iterador abstracto. IEnumerable tiene un único método GetEnumerator que es responsable de crear una instancia de IEnumerator del tipo deseado. Colecciones como Lists implementan IEnumerable.

Ejemplo. Supongamos que tenemos un método getPermutations(inputString) que devuelve todas las permutaciones de una cadena y que el método devuelve una instancia de IEnumerable

Para contar el número de permutaciones, podríamos hacer algo como lo siguiente.

  int count = 0; var permutations = perm.getPermutations(inputString); foreach (string permutation in permutations) { count++; } 

El comstackdor de c # convierte más o menos lo anterior a

 using (var permutationIterator = perm.getPermutations(input).GetEnumerator()) { while (permutationIterator.MoveNext()) { count++; } } 

Si tiene alguna pregunta, no dude en preguntar.

Una contribución menor.

Como muchos de ellos explican sobre ‘cuándo usar’ y ‘usar con foreach’. Pensé en agregar Otra Diferencia de Estados aquí como se solicita en cuestión sobre la diferencia entre ambos, IEnumerable y IEnumerator.

Creé el ejemplo de código a continuación basado en los hilos de discusión a continuación.

IEnumerable, IEnumerator vs foreach, cuándo usar What ¿ Cuál es la diferencia entre IEnumerator e IEnumerable?

El enumerador conserva el estado (posición de iteración) entre las llamadas de función mientras que las iteraciones de la otra mano no lo hacen.

Aquí está el ejemplo probado con comentarios para comprender.

Los expertos me agregan / corrigen.

 static void EnumerableVsEnumeratorStateTest() { IList numList = new List(); numList.Add(1); numList.Add(2); numList.Add(3); numList.Add(4); numList.Add(5); numList.Add(6); Console.WriteLine("Using Enumerator - Remembers the state"); IterateFrom1to3(numList.GetEnumerator()); Console.WriteLine("Using Enumerable - Does not Remembers the state"); IterateFrom1to3Eb(numList); Console.WriteLine("Using Enumerable - 2nd functions start from the item 1 in the collection"); } static void IterateFrom1to3(IEnumerator numColl) { while (numColl.MoveNext()) { Console.WriteLine(numColl.Current.ToString()); if (numColl.Current > 3) { // This method called 3 times for 3 items (4,5,6) in the collection. // It remembers the state and displays the continued values. IterateFrom3to6(numColl); } } } static void IterateFrom3to6(IEnumerator numColl) { while (numColl.MoveNext()) { Console.WriteLine(numColl.Current.ToString()); } } static void IterateFrom1to3Eb(IEnumerable numColl) { foreach (int num in numColl) { Console.WriteLine(num.ToString()); if (num>= 5) { // The below method invokes for the last 2 items. //Since it doesnot persists the state it will displays entire collection 2 times. IterateFrom3to6Eb(numColl); } } } static void IterateFrom3to6Eb(IEnumerable numColl) { Console.WriteLine(); foreach (int num in numColl) { Console.WriteLine(num.ToString()); } } 

Me he dado cuenta de estas diferencias:

R. Realizamos una iteración de la lista de diferentes maneras, foreach puede usarse para IEnumerable y while loop para IEnumerator.

B. IEnumerator puede recordar el índice actual cuando pasamos de un método a otro (comienza a trabajar con el índice actual), pero IEnumerable no puede recordar el índice y restablece el índice al principio. Más en este video https://www.youtube.com/watch?v=jd3yUjGc9M0

 using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Enudemo { class Person { string name = ""; int roll; public Person(string name, int roll) { this.name = name; this.roll = roll; } public override string ToString() { return string.Format("Name : " + name + "\t Roll : " + roll); } } class Demo : IEnumerable { ArrayList list1 = new ArrayList(); public Demo() { list1.Add(new Person("Shahriar", 332)); list1.Add(new Person("Sujon", 333)); list1.Add(new Person("Sumona", 334)); list1.Add(new Person("Shakil", 335)); list1.Add(new Person("Shruti", 336)); } IEnumerator IEnumerable.GetEnumerator() { return list1.GetEnumerator(); } } class Program { static void Main(string[] args) { Demo d = new Demo(); // Notice here. it is simple object but for //IEnumerator you can get the collection data foreach (Person X in d) { Console.WriteLine(X); } Console.ReadKey(); } } } /* Output : Name : Shahriar Roll : 332 Name : Sujon Roll : 333 Name : Sumona Roll : 334 Name : Shakil Roll : 335 Name : Shruti Roll : 336 */