que - yield return list c#
Algunos ayudan a entender el "rendimiento" (8)
"rendimiento" crea un bloque de iterador, una clase generada por el compilador que puede implementar IEnumerable[<T>]
o IEnumerator[<T>]
. 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[<T>]
o un IEnumerator[<T>]
. En este caso:
public IEnumerable<AClass> SomeMethod() {
// ...
foreach (XElement header in headersXml.Root.Elements()){
yield return (ParseHeader(header));
}
}
En mi búsqueda eterna de aspirar menos, trato de entender la declaración de "rendimiento", pero sigo encontrando el mismo error.
El cuerpo de [someMethod] no puede ser un bloque iterador porque ''System.Collections.Generic.List <AClass>'' 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<ProductMixHeader>
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<AClass>
como un tipo de devolución, pero como List<T>
no es flojo, no puede. Al cambiar mi tipo de devolución a IEnumerable<T>
resolvió el problema: D
Una pregunta un tanto relacionada (no vale la pena abrir un nuevo hilo): ¿vale la pena dar IEnumerable<T>
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?
¿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<string> GetValues() {
foreach(string value in someArray) {
if (value.StartsWith("A")) { yield return value; }
}
}
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<int> 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 compilador genera para usted cuando usa yield, y descubrí que las personas entienden el concepto mucho más rápido cuando pueden ver el resultado de bajo nivel (bueno, nivel, supongo)
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<int> FindPrimesWithYield()
{
int i;
for (i = 1; i < 2147483647; i++)
{
if (isPrime(i))
{
yield return i;
}
}
}
public IEnumerable<int> FindPrimesWithoutYield()
{
var primes = new List<int>();
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();
}
}
}
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<int>
y otro que devuelve IEnumerable<int>
. 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<int> IterateOne(Func<int, bool> condition)
{
for(var i=1; condition(i); i++) { yield return i; }
}
public static IEnumerable<int> IterateAll(Func<int, bool> 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<T>
e IEnumerable<T>
son genéricos, 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<ProductMixHeader>
o IEnumerable<ProductMixHeader>
(la clase personalizada que ha mencionado en su pregunta).
El tipo List<ProductMixHeader>
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.
Es un tema complicado. En pocas palabras, es una forma sencilla de implementar IEnumerable y sus amigos. El compilador 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)
Un método que usa rendimiento devuelto debe declararse como devolver una de las siguientes dos interfaces:
IEnumerable<SomethingAppropriate>
IEnumerator<SomethingApropriate>
(Gracias Jon y Marc por señalar IEnumerator)
Ejemplo:
public IEnumerable<AClass> 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.