waitforseconds que c# iterator ienumerable yield

que - yield return c# stack overflow



¿Valor agregado de la palabra clave de rendimiento? (10)

El rendimiento le permite construir métodos que producen datos sin tener que reunir todo antes de regresar. Piénselo como devolver múltiples valores en el camino.

Aquí hay un par de métodos que ilustran el punto

public IEnumerable<String> LinesFromFile(String fileName) { using (StreamReader reader = new StreamReader(fileName)) { String line; while ((line = reader.ReadLine()) != null) yield return line; } } public IEnumerable<String> LinesWithEmails(IEnumerable<String> lines) { foreach (String line in lines) { if (line.Contains("@")) yield return line; } }

Ninguno de estos dos métodos leerá todo el contenido del archivo en la memoria, sin embargo, puede usarlos así:

foreach (String lineWithEmail in LinesWithEmails(LinesFromFile("test.txt"))) Console.Out.WriteLine(lineWithEmail);

todavía estoy tratando de encontrar dónde usaría la palabra clave "yield" en una situación real.

Veo este hilo sobre el tema

¿Cuál es la palabra clave yield utilizada en C #?

pero en la respuesta aceptada, tienen esto como un ejemplo donde alguien está iterando alrededor de enteros ()

public IEnumerable<int> Integers() { yield return 1; yield return 2; yield return 4; yield return 8; yield return 16; yield return 16777216; }

pero ¿por qué no solo usar

list<int>

aquí en cambio. parece más sencillo ...


Es posible que desee iterar a través de varias colecciones:

public IEnumerable<ICustomer> Customers() { foreach( ICustomer customer in m_maleCustomers ) { yield return customer; } foreach( ICustomer customer in m_femaleCustomers ) { yield return customer; } // or add some constraints... foreach( ICustomer customer in m_customers ) { if( customer.Age < 16 ) { yield return customer; } } // Or.... if( Date.Today == 1 ) { yield return m_superCustomer; } }


Es posible que no siempre desee usar yield en lugar de devolver una lista, y en su ejemplo utiliza yield para devolver realmente una lista de enteros. Dependiendo de si desea una lista mutable, o una secuencia inmutable, puede usar una lista, o un iterador (o alguna otra colección muttable / inmutable).

Pero hay beneficios para usar el rendimiento.

  • El rendimiento proporciona una manera fácil de construir iteradores evaluados perezosos. (Lo que significa que solo el código para obtener el próximo elemento en la secuencia se ejecuta cuando se llama al método MoveNext () y luego el iterador no realiza más cálculos hasta que se vuelve a llamar al método)

  • El rendimiento construye una máquina de estado debajo de las cubiertas, y esto le ahorra mucho trabajo al no tener que codificar los estados de su generador genérico => más código conciso / simple.

  • El rendimiento construye automáticamente iteradores optimizados e hilos seguros, lo que le ahorra los detalles sobre cómo construirlos.

  • El rendimiento es mucho más poderoso de lo que parece a primera vista y se puede usar para mucho más que construir simples iteradores, vea este video para ver a Jeffrey Richter y su AsyncEnumerator y cómo se usa el rendimiento para hacer que la codificación usando el patrón asíncrono sea fácil.


Estoy de acuerdo con todo lo que todos han dicho aquí sobre la evaluación diferida y el uso de la memoria, y quería agregar otro escenario en el que encuentre útiles los iteradores que utilizan la palabra clave yield . Me he encontrado con algunos casos en los que tengo que hacer una secuencia de procesamiento potencialmente costoso en algunos datos donde es extremadamente útil usar iteradores. En lugar de procesar todo el archivo de forma inmediata, o ampliar mi propio canal de procesamiento, simplemente puedo usar iteradores como este:

IEnumerable<double> GetListFromFile(int idxItem) { // read data from file return dataReadFromFile; } IEnumerable<double> ConvertUnits(IEnumerable<double> items) { foreach(double item in items) yield return convertUnits(item); } IEnumerable<double> DoExpensiveProcessing(IEnumerable<double> items) { foreach(double item in items) yield return expensiveProcessing(item); } IEnumerable<double> GetNextList() { return DoExpensiveProcessing(ConvertUnits(GetListFromFile(curIdx++))); }

La ventaja aquí es que al mantener la entrada y salida a todas las funciones IEnumerable<double> , mi IEnumerable<double> procesamiento es completamente composable, fácil de leer y de evaluación lenta, así que solo tengo que hacer el procesamiento que realmente necesito hacer. Esto me permite poner casi todo mi procesamiento en el hilo de la GUI sin afectar la capacidad de respuesta, por lo que no tengo que preocuparme por problemas de subprocesos.


Puedes usar yield para construir cualquier iterador. Eso podría ser una serie perezosamente evaluada (líneas de lectura de un archivo o base de datos, por ejemplo, sin leer todo de una vez, que podría ser demasiado para mantener en la memoria), o podría estar iterando sobre datos existentes como una List<T> .

C # en profundidad tiene un capítulo libre (6) sobre bloques de iteradores.

También publiqué recientemente un blog sobre el uso de yield para algoritmos inteligentes de fuerza bruta.

Para un ejemplo del lector de archivos perezosa:

static IEnumerable<string> ReadLines(string path) { using (StreamReader reader = File.OpenText(path)) { string line; while ((line = reader.ReadLine()) != null) { yield return line; } } }

Esto es completamente "flojo"; no se lee nada hasta que empiezas a enumerar, y solo se guarda una sola línea en la memoria.

Tenga en cuenta que LINQ-to-Objects hace un uso extensivo de bloques iteradores ( yield ). Por ejemplo, la extensión Where es esencialmente:

static IEnumerable<T> Where<T>(this IEnumerable<T> data, Func<T, bool> predicate) { foreach (T item in data) { if (predicate(item)) yield return item; } }

Y de nuevo, completamente vago: permitiéndole encadenar varias operaciones sin forzar que todo se cargue en la memoria.


Si compila y devuelve una lista (digamos que tiene 1 millón de elementos), eso es una gran parte de la memoria y también de trabajo para crearla.

A veces, la persona que llama solo querrá saber cuál es el primer elemento. O tal vez quieran escribirlos en un archivo a medida que los obtienen, en lugar de construir toda la lista en la memoria y luego escribirla en un archivo.

Es por eso que tiene más sentido usar el retorno de rendimiento. No se ve tan diferente a construir toda la lista y devolverla, pero es muy diferente porque no es necesario crear toda la lista en la memoria antes de que la persona que llama pueda ver el primer elemento en ella.

Cuando la persona que llama dice:

foreach (int i in Integers()) { // do something with i }

Cada vez que el ciclo requiere una nueva i, ejecuta un poco más del código en enteros (). El código en esa función está "en pausa" cuando golpea una declaración de yield return .


Una situación real para mí es cuando quiero procesar una colección que tarda un tiempo en poblarse más fácilmente.

Imagina algo en la línea (código psuedo):

public IEnumberable<VerboseUserInfo> GetAllUsers() { foreach(UserId in userLookupList) { VerboseUserInfo info = new VerboseUserInfo(); info.Load(ActiveDirectory.GetLotsOfUserData(UserId)); info.Load(WebSerice.GetSomeMoreInfo(UserId)); yield return info; } }

En lugar de tener que esperar un minuto para que se llene la colección antes de que pueda comenzar a procesar elementos en ella. Podré comenzar de inmediato y luego informar a la interfaz del usuario cuando ocurra.


yield le permite procesar colecciones que son potencialmente de un tamaño infinito porque la colección completa nunca se carga en la memoria de una vez, a diferencia de un enfoque basado en listas. Por ejemplo, un IEnumerable <> de todos los números primos podría ser retirado por el algoritmo apropiado para encontrar los números primos, mientras que un enfoque de lista siempre sería de tamaño finito y, por lo tanto, incompleto. En este ejemplo, usar el rendimiento también permite que el procesamiento para el siguiente elemento se difiera hasta que se requiera.


Se me ocurrió esto para superar la falla de .NET al tener que copiar manualmente la Lista.

Yo uso esto:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements) { foreach (SpotPlacement sp in spotPlacements) { yield return (SpotPlacement)sp.Clone(); } }

Y en otro lugar:

public object Clone() { OrderItem newOrderItem = new OrderItem(); ... newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements)); ... return newOrderItem; }

Traté de encontrar un "oneliner" que haga esto, pero no es posible, debido a que el rendimiento no funciona dentro de bloques de métodos anónimos.

EDITAR:

Mejor aún, use el clonador genérico List:

class Utility<T> where T : ICloneable { static public IEnumerable<T> CloneList(List<T> tl) { foreach (T t in tl) { yield return (T)t.Clone(); } } }


El método utilizado por el yield de la memoria de almacenamiento mediante el procesamiento de elementos sobre la marcha es bueno, pero en realidad se trata de azúcar sintáctica. Ha existido por mucho tiempo. En cualquier idioma que tenga punteros de función o interfaz (incluso C y ensamblaje) puede obtener el mismo efecto utilizando una función / interfaz de devolución de llamada.

Estas cosas elegantes:

static IEnumerable<string> GetItems() { yield return "apple"; yield return "orange"; yield return "pear"; } foreach(string item in GetItems()) { Console.WriteLine(item); }

es básicamente equivalente a anticuado:

interface ItemProcessor { void ProcessItem(string s); }; class MyItemProcessor : ItemProcessor { public void ProcessItem(string s) { Console.WriteLine(s); } }; static void ProcessItems(ItemProcessor processor) { processor.ProcessItem("apple"); processor.ProcessItem("orange"); processor.ProcessItem("pear"); } ProcessItems(new MyItemProcessor());