Algunos ayudan a entender el “rendimiento”

En mi búsqueda eterna de aspirar menos, trato de entender la statement de “rendimiento”, pero sigo encontrando el mismo error.

El cuerpo de [someMethod] no puede ser un bloque iterador porque ‘System.Collections.Generic.List ‘ no es un tipo de interfaz de iterador.

Este es el código donde me quedé atrapado:

foreach (XElement header in headersXml.Root.Elements()){ yield return (ParseHeader(header)); } 

¿Qué estoy haciendo mal? ¿No puedo usar yield en un iterador? Entonces, ¿cuál es el punto? En este ejemplo, dijo que List no es un tipo de interfaz de iterador. ProductMixHeader es una clase personalizada, pero imagino que List es un tipo de interfaz de iterador, ¿no?

–Editar–
Gracias por todas las respuestas rápidas.
Sé que esta pregunta no es tan nueva y los mismos recursos siguen apareciendo.
Resultó que estaba pensando que podía devolver List como un tipo de devolución, pero como List no es flojo, no puede. Al cambiar mi tipo de devolución a IEnumerable resolvió el problema: D

Una pregunta un tanto relacionada (no vale la pena abrir un nuevo hilo): ¿vale la pena dar IEnumerable como un tipo de devolución si estoy seguro de que el 99% de los casos voy a ir a .ToList () de todos modos? ¿Cuáles serán las implicaciones de rendimiento?

Un método que usa rendimiento devuelto debe declararse como devolver una de las siguientes dos interfaces:

 IEnumerable IEnumerator 

(Gracias Jon y Marc por señalar IEnumerator)

Ejemplo:

 public IEnumerable YourMethod() { foreach (XElement header in headersXml.Root.Elements()) { yield return (ParseHeader(header)); } } 

yield es un productor de datos perezosa, que solo produce otro artículo después de que se ha recuperado el primero, mientras que devolver una lista devolverá todo de una vez.

Entonces hay una diferencia, y necesitas declarar el método correctamente.

Para obtener más información, lea la respuesta de Jon aquí , que contiene algunos enlaces muy útiles.

Es un tema complicado. En pocas palabras, es una forma sencilla de implementar IEnumerable y sus amigos. El comstackdor crea una máquina de estados, transformando parámetros y variables locales en variables de instancia en una nueva clase. Cosas complicadas

Tengo algunos recursos sobre esto:

  • Capítulo 6 de C # en profundidad (descarga gratuita desde esa página)
  • Iteradores, bloques de iteradores y canalizaciones de datos (artículo)
  • Detalles de implementación del bloque iterador (artículo)

“rendimiento” crea un bloque de iterador, una clase generada por el comstackdor que puede implementar IEnumerable[] o IEnumerator[] . Jon Skeet tiene una discusión muy buena (y gratuita) sobre esto en el capítulo 6 de C # en profundidad .

Pero, básicamente, para usar “rendimiento” su método debe devolver un IEnumerable[] o un IEnumerator[] . En este caso:

 public IEnumerable SomeMethod() { // ... foreach (XElement header in headersXml.Root.Elements()){ yield return (ParseHeader(header)); } } 

Lista implementa Ienumerable.

Aquí hay un ejemplo que podría arrojar algo de luz sobre lo que estás tratando de aprender. Lo escribí hace 6 meses

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace YieldReturnTest { public class PrimeFinder { private Boolean isPrime(int integer) { if (0 == integer) return false; if (3 > integer) return true; for (int i = 2; i < integer; i++) { if (0 == integer % i) return false; } return true; } public IEnumerable FindPrimes() { int i; for (i = 1; i < 2147483647; i++) { if (isPrime(i)) { yield return i; } } } } class Program { static void Main(string[] args) { PrimeFinder primes = new PrimeFinder(); foreach (int i in primes.FindPrimes()) { Console.WriteLine(i); Console.ReadLine(); } Console.ReadLine(); Console.ReadLine(); } } } 

Recomiendo utilizar Reflector para ver qué yield realmente hace por ti. Podrá ver el código completo de la clase que el comstackdor genera para usted cuando usa yield, y he descubierto que las personas entienden el concepto mucho más rápido cuando pueden ver el resultado de bajo nivel (bueno, nivel, supongo)

Para comprender el yield , debe comprender cuándo usar IEnumerator e IEnumerable (porque debe usar cualquiera de ellos). Los siguientes ejemplos lo ayudan a comprender la diferencia.

Primero, eche un vistazo a la siguiente clase, implementa dos métodos: uno que devuelve IEnumerator y otro que devuelve IEnumerable . Le mostraré que hay una gran diferencia en el uso, aunque el código de los 2 métodos tiene un aspecto similar:

 // 2 iterators, one as IEnumerator, one as IEnumerable public class Iterator { public static IEnumerator IterateOne(Func condition) { for(var i=1; condition(i); i++) { yield return i; } } public static IEnumerable IterateAll(Func condition) { for(var i=1; condition(i); i++) { yield return i; } } } 

Ahora, si usa IterateOne , puede hacer lo siguiente:

  // 1. Using IEnumerator allows to get item by item var i=Iterator.IterateOne(x => true); // iterate endless // 1.a) get item by item i.MoveNext(); Console.WriteLine(i.Current); i.MoveNext(); Console.WriteLine(i.Current); // 1.b) loop until 100 int j; while (i.MoveNext() && (j=i.Current)<=100) { Console.WriteLine(j); } 

1.a) impresiones:

1
2

1.b) impresiones:

3
4
...
100

porque continúa contando justo después de que se hayan ejecutado las afirmaciones 1.a).

Puede ver que puede avanzar elemento por elemento usando MoveNext() .


Por el contrario, IterateAll permite usar foreach y también declaraciones LINQ para una mayor comodidad:

  // 2. Using IEnumerable makes looping and LINQ easier var k=Iterator.IterateAll(x => x<100); // limit iterator to 100 // 2.a) Use a foreach loop foreach(var x in k){ Console.WriteLine(x); } // loop // 2.b) LINQ: take 101..200 of endless iteration var lst=Iterator.IterateAll(x=>true).Skip(100).Take(100).ToList(); // LINQ: take items foreach(var x in lst){ Console.WriteLine(x); } // output list 

2.a) impresiones:

1
2
...
99

2.b) impresiones:

101
102
...
200


Nota: Como IEnumerator e IEnumerable son generics, se pueden usar con cualquier tipo. Sin embargo, para simplificar he usado int en mis ejemplos para el tipo T

Esto significa que puede usar uno de los tipos de devolución IEnumerator o IEnumerable (la clase personalizada que ha mencionado en su pregunta).

El tipo List no implementa ninguna de estas interfaces, que es la razón por la que no puede usarlo de esa manera. Pero el Ejemplo 2.b) muestra cómo puedes crear una lista a partir de él.

¿Cómo se ve el método que estás usando? No creo que esto pueda usarse solo en un ciclo en sí mismo.

Por ejemplo…

 public IEnumerable GetValues() { foreach(string value in someArray) { if (value.StartsWith("A")) { yield return value; } } } 

La respuesta de @Ian P me ayudó mucho a entender el rendimiento y por qué se usa. Un caso de uso (principal) para el rendimiento se encuentra en los bucles “foreach” después de la palabra clave “in” para no devolver una lista completa. En lugar de devolver una lista completa a la vez, en cada ciclo “foreach” solo se devuelve un elemento (el siguiente elemento). Por lo tanto, obtendrá rendimiento con rendimiento en tales casos. He reescrito el código de @Ian P para una mejor comprensión de lo siguiente:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace YieldReturnTest { public class PrimeFinder { private Boolean isPrime(int integer) { if (0 == integer) return false; if (3 > integer) return true; for (int i = 2; i < integer; i++) { if (0 == integer % i) return false; } return true; } public IEnumerable FindPrimesWithYield() { int i; for (i = 1; i < 2147483647; i++) { if (isPrime(i)) { yield return i; } } } public IEnumerable FindPrimesWithoutYield() { var primes = new List(); int i; for (i = 1; i < 2147483647; i++) { if (isPrime(i)) { primes.Add(i); } } return primes; } } class Program { static void Main(string[] args) { PrimeFinder primes = new PrimeFinder(); Console.WriteLine("Finding primes until 7 with yield...very fast..."); foreach (int i in primes.FindPrimesWithYield()) // FindPrimesWithYield DOES NOT iterate over all integers at once, it returns item by item { if (i > 7) { break; } Console.WriteLine(i); //Console.ReadLine(); } Console.WriteLine("Finding primes until 7 without yield...be patient it will take lonkg time..."); foreach (int i in primes.FindPrimesWithoutYield()) // FindPrimesWithoutYield DOES iterate over all integers at once, it returns the complete list of primes at once { if (i > 7) { break; } Console.WriteLine(i); //Console.ReadLine(); } Console.ReadLine(); Console.ReadLine(); } } }