c# - unity - ¿Hay alguna razón para no usar el ''rendimiento de rendimiento'' cuando se devuelve un IEnumerable?
yield return c# stack overflow (4)
Extraña pregunta. Si su método está devolviendo un IEnumerable
que se obtiene de otro lugar, obviamente no utilizará el yield return
. Si su método necesita ensamblar una estructura de datos concreta que represente los resultados para realizar alguna manipulación antes de regresar, entonces supongo que tampoco utilizará el yield return
.
Ejemplo simple: tiene un método o una propiedad que devuelve un IEnumerable y la persona que llama está repitiendo en un bucle foreach (). ¿Debería utilizar siempre el ''rendimiento de rendimiento'' en su método IEnumerable? ¿Hay alguna razón para no hacerlo? Si bien entiendo que puede no ser siempre necesario, o incluso "mejor" (por ejemplo, es una colección muy pequeña), ¿existe alguna razón para evitar activamente hacer esto?
La parte del código que me hizo pensar en esto fue una función que escribí muy similar a la respuesta aceptada en este hilo: ¿Cómo hago un bucle en un rango de fechas?
No lo creo. Como lo sugiere @LBushkin, si regresara algo como un todo, devolvería un IList o lo que sea. Si devuelve un IEnumerable, la gente espera una ejecución diferida, por lo que creo que siempre debe usar el rendimiento en ese caso.
Una razón clara para no usar un enumerador es cuando necesita que IEnumerator<>.Reset()
funcione.
Los iteradores son muy agradables, pero no pueden escapar al principio de "no hay almuerzo gratis". No los encontrará usados en el código de colección de .NET Framework. Hay una buena razón para eso, no pueden ser tan eficientes como una implementación dedicada. Ahora que eso era importante para los diseñadores .NET, no podían predecir cuándo la eficiencia es importante. Usted puede, usted sabe si su código está en la ruta crítica de su programa.
Los iteradores son un poco más del doble de lentos que una implementación dedicada. Al menos eso es lo que medí probando el iterador de List<>
. Cuidado con las micro optimizaciones, todavía son muy rápidas y su gran Oh es el mismo.
Incluiré el código de prueba para que puedas verificar esto por ti mismo:
using System;
using System.Collections.Generic;
using System.Diagnostics;
class Program {
static void Main(string[] args) {
var lst = new MyList<int>();
for (int ix = 0; ix < 10000000; ++ix) lst.Add(ix);
for (int test = 0; test < 20; ++test) {
var sw1 = Stopwatch.StartNew();
foreach (var item in lst) ;
sw1.Stop();
var sw2 = Stopwatch.StartNew();
foreach (var item in lst.GetItems()) ;
sw2.Stop();
Console.WriteLine("{0} {1}", sw1.ElapsedMilliseconds, sw2.ElapsedMilliseconds);
}
Console.ReadLine();
}
}
class MyList<T> : IList<T> {
private List<T> lst = new List<T>();
public IEnumerable<T> GetItems() {
foreach (T item in lst)
yield return item;
}
public int IndexOf(T item) { return lst.IndexOf(item); }
public void Insert(int index, T item) { lst.Insert(index, item); }
public void RemoveAt(int index) { lst.RemoveAt(index); }
public T this[int index] {
get { return lst[index]; }
set { lst[index] = value; }
}
public void Add(T item) { lst.Add(item); }
public void Clear() { lst.Clear(); }
public bool Contains(T item) { return lst.Contains(item); }
public void CopyTo(T[] array, int arrayIndex) { lst.CopyTo(array, arrayIndex); }
public int Count { get { return lst.Count; } }
public bool IsReadOnly { get { return ((IList<T>)lst).IsReadOnly; } }
public bool Remove(T item) { return lst.Remove(item); }
public IEnumerator<T> GetEnumerator() { return lst.GetEnumerator(); }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
Los bloques de iteradores realizan una evaluación "en vivo" cada vez que se iteran.
A veces, sin embargo, el comportamiento que desea es que los resultados sean una "instantánea" en un punto en el tiempo. En estos casos, es probable que no desee utilizar el yield return
, sino que, en cambio, devuelva una List<>
o un Set
, o alguna otra colección persistente.
También es innecesario utilizar el yield return
si está tratando con objetos de consulta directamente. Este es a menudo el caso de las consultas LINQ: es mejor simplemente devolver IEnumerable<>
desde la consulta en lugar de iterar y yield return
resultados de yield return
usted mismo. Por ejemplo:
var result = from obj in someCollection
where obj.Value < someValue
select new { obj.Name, obj.Value };
foreach( var item in result )
yield return item; // THIS IS UNNECESSARY....
// just return {result} instead...