2008-12-21 9 views
8

tratando de encontrar donde utilizaría la palabra clave "yield" en una situación real.¿Valor añadido de la palabra clave de rendimiento?

veo este hilo sobre el tema

What is the yield keyword used for in C#?

pero en la respuesta aceptada, tienen esto como un ejemplo donde alguien está iterando alrededor 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 use

list<int> 

aquí en su lugar. Parece más sencillo ..

+0

posible duplicado de (http://stackoverflow.com/questions/17125/what-are-real-life-applications-of-yield) – nawfal

+0

Puede encontrar una respuesta aquí: http://stackoverflow.com/questions/14057788/why-use-the-yield-keyword-when -i-could-just-use-an-ordinary-ienumerable – hdoghmen

Respuesta

23

Si se construye y devuelve una lista (dicen que tiene 1 millón de elementos), que es una gran cantidad de memoria, y también de trabajo para crearlo.

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.

Por eso tiene más sentido utilizar 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 bucle requiere un nuevo i, se ejecuta un poco más de un código de números enteros(). El código en esa función está "en pausa" cuando golpea una declaración yield return.

+1

Tenía problemas para comprender el rendimiento. ¡Pero tu respuesta fue agradable! Creo que el uso del rendimiento se parece más o menos a la diferencia entre el uso de DataReader y DataSets. Con DataSets obtenemos todos los datos, luego trabajamos y DataReaders puede trabajar con los datos mientras llega desde el origen. :-) –

0

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; 
     } 

} 
+1

Si está interesado (y desconoce Linq), puede escribir th en todo esto como: return m_maleCustomers.Concat (m_femaleCustomers) .Concat (m_customers.Where (c => c.Age <16)). Concat (Enumerable.Repeat (m_superCustomer, 1) .Where (Date.Today == 1) ; –

4

Puede usar yield para compilar cualquier iterador. Esa 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 guardar en la memoria), o podría iterar sobre datos existentes como List<T>.

C# in Depth tiene un capítulo libre (6) todo sobre bloques de iterador.

Yo también blogged muy recientemente sobre el uso de yield para los algoritmos inteligentes de fuerza bruta.

Para un ejemplo del lector de archivos perezoso:

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 "perezosa"; nada se lee hasta que empiece a enumerar, y solo se guarda una sola línea en la memoria.

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

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, totalmente vago - lo que le permite encadenar múltiples operaciones sin forzar todo para ser cargado en la memoria.

+0

Buen artículo sobre la fuerza bruta perezosa, ¿qué tal si esperas una lista con un objeto singel, usarías un simple() para asegurarte? ¿es una buena práctica? – CloudyMarble

+0

@CloudyMarble seguro, esa es una forma perfectamente razonable de asegurarse de que se valida.Tenga en cuenta que 'First()' puede ser * más barato *, sin embargo, evita tener que buscar un segundo elemento, por lo que depende de si desea afirmar "al menos uno" frente a "exactamente uno" –

9

El rendimiento le permite crear métodos que producen datos sin tener que reunir todo antes de volver. 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 se lee todo el contenido del archivo en la memoria, sin embargo, se puede utilizar como esto:

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

rendimiento le permite procesar colecciones que son potencialmente infinitas en tamaño porque la colección completa nunca se carga en la memoria de una vez, a diferencia de un enfoque basado en la lista. Por ejemplo, un IEnumerable <> de todos los números primos podría ser anulado 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.

1

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

imaginar algo a lo largo de las líneas (pseudo código):

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 la recogida de poblar antes de que pueda comenzar a procesar los elementos de la misma. Podré comenzar de inmediato y luego informar a la interfaz del usuario cuando ocurra.

0

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 usando 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 el archivo completo de forma inmediata, o rodar mi propia canalización de procesamiento, simplemente puedo usar iteradores algo como esto:

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 de todas las funciones IEnumerable<double>, mi El pipeline de 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 ningún problema de subprocesos.

1

Puede que no siempre desee utilizar el rendimiento en lugar de devolver una lista, y en su ejemplo utiliza el rendimiento 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 ventajas en 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 siguiente elemento en 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)

  • Rendimiento genera una máquina de estado bajo 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.

  • Rendimiento construye automáticamente iteradores optimizados y resistentes a hilos, 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 puede usarse para mucho más que crear iteradores simples, vea este video para ver Jeffrey Richter and his AsyncEnumerator y cómo se usa el rendimiento para facilitar la codificación mediante el patrón asíncrono.

0

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

utilizo este:

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 llegar a oneliner que hace esto, pero no es posible, debido a un rendimiento no se trabaja dentro anónima bloques de métodos.

EDIT:

Mejor aún, el uso genérico clonador de lista:

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

El método utilizado por yield de salvar la memoria mediante el procesamiento de artículos sobre la marcha es agradable, pero en realidad es sólo azúcar sintáctico . 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.

Esta materia de lujo:

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 la antigua usanza: [? ¿Cuáles son las aplicaciones de la vida real de rendimiento]

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()); 
+0

No es realmente equivalente, ya que cada enfoque permite cosas que no se pueden hacer en el otro. Por ejemplo, es posible iterar dos IEnumerables "en paralelo" para algo así como una operación de fusión; tal cosa sería imposible con el enfoque de delegar-pasar. Por otro lado, sería posible que un método estilo "DoForEach" como ProcessItems aceptara un parámetro por referencia y lo pasara por referencia al delegado anidado; tales métodos también envuelven las llamadas de elementos anidados en bloques "de prueba" y no tienen que preocuparse por ser abandonados sin estar Dispuestos. – supercat

Cuestiones relacionadas