Un uso práctico de la palabra clave "yield" en C # [cerrado]

72

Después de casi 4 años de experiencia, no he visto un código donde se use la palabra clave rendimiento . ¿Puede alguien mostrarme un uso práctico (junto con la explicación) de esta palabra clave, y si es así, no hay otras formas más fáciles de cumplir que puede hacer?

    
pregunta Saeed Neamati 30.07.2011 - 20:01

8 respuestas

102

Eficiencia

La palabra clave yield crea efectivamente una enumeración perezosa sobre los elementos de la colección que pueden ser mucho más eficientes. Por ejemplo, si su bucle foreach itera solo sobre los primeros 5 elementos de 1 millón de artículos, entonces eso es todo yield devuelve, y no creó una colección de 1 millón de artículos internamente primero. Del mismo modo, deseará usar yield con IEnumerable<T> de los valores de retorno en sus propios escenarios de programación para lograr las mismas eficiencias.

Ejemplo de eficiencia obtenida en un escenario determinado

No es un método de iterador, el uso ineficiente potencial de una gran colección,
(La colección intermedia se construye con muchos elementos)

// Method returns all million items before anything can loop over them. 
List<object> GetAllItems() {
    List<object> millionCustomers;
    database.LoadMillionCustomerRecords(millionCustomers); 
    return millionCustomers;
}

// MAIN example ---------------------
// Caller code sample:
int num = 0;
foreach(var itm in GetAllItems())  {
    num++;
    if (num == 5)
        break;
}
// Note: One million items returned, but only 5 used. 

Versión de iterador, eficiente
(No se crea una colección intermedia)

// Yields items one at a time as the caller's foreach loop requests them
IEnumerable<object> IterateOverItems() {
    for (int i; i < database.Customers.Count(); ++i)
        yield return database.Customers[i];
}

// MAIN example ---------------------
// Caller code sample:
int num = 0;
foreach(var itm in IterateOverItems())  {
    num++;
    if (num == 5)
        break;
}
// Note: Only 5 items were yielded and used out of the million.

Simplifique algunos escenarios de programación

En otro caso, hace que algunos tipos de clasificación y fusión de listas sean más fáciles de programar porque usted simplemente yield artículos nuevamente en el orden deseado en lugar de clasificarlos en una colección intermedia e intercambiarlos allí. Hay muchos de esos escenarios.

Un solo ejemplo es la combinación de dos listas:

IEnumerable<object> EfficientMerge(List<object> list1, List<object> list2) {
    foreach(var o in list1) 
        yield return o; 
    foreach(var o in list2) 
        yield return o;
}

Este método devuelve una lista contigua de elementos, de hecho, una combinación sin necesidad de recopilación intermedia.

Más información

La palabra clave yield solo se puede usar en el contexto de un método iterador (que tiene un tipo de retorno de IEnumerable , IEnumerator , IEnumerable<T> o IEnumerator<T> .) y existe una relación especial con% código%. Los iteradores son métodos especiales. La documentación de rendimiento de MSDN y documentación del iterador contiene mucha información interesante y una explicación de los conceptos. Asegúrese de correlacionarlo con la palabra clave foreach por leyendo sobre esto también, para complementar su comprensión de los iteradores.

Para aprender cómo los iteradores logran su eficiencia, el secreto está en el código IL generado por el compilador de C #. La IL generada para un método de iterador difiere drásticamente de la generada para un método regular (no iterador). Este artículo (¿Qué genera realmente la palabra clave de rendimiento?) proporciona ese tipo de información.

    
respondido por el John K 31.07.2011 - 05:56
4

Hace un tiempo tuve un ejemplo práctico, supongamos que tienes una situación como esta:

List<Button> buttons = new List<Button>();
void AddButtons()
{
   for ( int i = 0; i <= 10; i++ ) {
      var button = new Button();
      buttons.Add(button);
      button.Click += (sender, e) => 
          MessageBox.Show(String.Format("You clicked button number {0}", ???));
   }
}

El objeto de botón no conoce su propia posición en la colección. La misma limitación se aplica a Dictionary<T> u otros tipos de colecciones.

Aquí está mi solución usando la palabra clave yield :

interface IHasId { int Id { get; set; } }

class IndexerList<T>: List<T>, IEnumerable<T> where T: IHasId
{
   List<T> elements = new List<T>();
   new public void Clear() { elements.Clear(); }
   new public void Add(T element) { elements.Add(element); }
   new public int Count { get { return elements.Count; } }    
   new public IEnumerator<T> GetEnumerator()
   {
      foreach ( T c in elements )
         yield return c;
   }

   new public T this[int index]
   {
      get
      {
         foreach ( T c in elements ) {
            if ( (int)c.Id == index )
               return c;
         }
         return default(T);
      }
   }
}

Y así es como lo uso:

class ButtonWithId: Button, IHasId
{
   public int Id { get; private set; }
   public ButtonWithId(int id) { this.Id = id; }
}

IndexerList<ButtonWithId> buttons = new IndexerList<ButtonWithId>();
void AddButtons()
{
   for ( int i = 10; i <= 20; i++ ) {
      var button = new ButtonWithId(i);
      buttons.Add(button);
      button.Click += (sender, e) => 
         MessageBox.Show(String.Format("You clicked button number {0}", ( (ButtonWithId)sender ).Id));
   }
}

No tengo que hacer un bucle for sobre mi colección para encontrar el índice. Mi botón tiene una ID y también se usa como índice en IndexerList<T> , así que evita cualquier ID o índice redundante . ¡Eso es lo que me gusta! El índice / Id puede ser un número arbitrario.

    
respondido por el Wernfried Domscheit 09.06.2015 - 19:33
2

Un ejemplo práctico se puede encontrar aquí:

enlace

Hay una serie de ventajas de usar el rendimiento sobre el código estándar:

  • Si el iterador se usa para crear una lista, puede devolver la declaración y la persona que llama puede decidir si quiere que el resultado sea una lista, o no.
  • La persona que llama también puede decidir cancelar la iteración por un motivo que está fuera del alcance de lo que está haciendo en la iteración.
  • El código es un poco más corto.

Sin embargo, como dijo Jan_V (solo dame unos segundos :-) puedes vivir sin él porque internamente el compilador producirá un código casi idéntico en ambos casos.

    
respondido por el Jalayn 30.07.2011 - 20:26
1

Aquí hay un ejemplo:

enlace

La clase realiza cálculos de fecha basados en una semana laboral. Puedo decir una instancia de la clase que Bob trabaja de 9:30 a 17:30 todos los días de la semana con un descanso de una hora para el almuerzo a las 12:30. Con este conocimiento, la función AscendingShifts () producirá objetos de turno de trabajo entre las fechas proporcionadas. Para enumerar todos los turnos de trabajo de Bob entre el 1 de enero y el 1 de febrero de este año, lo utilizaría así:

foreach (var shift in week.AscendingShifts(new DateTime(2011, 1, 1), new DateTime(2011, 2, 1)) {
    Console.WriteLine(shift);
}

La clase realmente no itera sobre una colección. Sin embargo, los cambios entre dos fechas se pueden considerar como una colección. El operador yield permite iterar sobre esta colección imaginada sin crear la colección en sí.

    
respondido por el Ant 31.07.2011 - 12:28
1

Tengo una pequeña capa de datos db que tiene una clase command en la que establece el texto del comando SQL, el tipo de comando y devuelve un IEnumerable de 'parámetros de comando'.

Básicamente, la idea es tener comandos CLR en lugar de llenar manualmente las propiedades y parámetros SqlCommand todo el tiempo.

Entonces hay una función que se parece a esto:

IEnumerable<DbParameter> GetParameters()
{
    // here i do something like

    yield return new DbParameter { name = "@Age", value = this.Age };

    yield return new DbParameter { name = "@Name", value = this.Name };
}

La clase que hereda esta clase command tiene las propiedades Age y Name .

Luego puedes agregar un objeto command lleno sus propiedades y pasarlo a una interfaz db que en realidad hace la llamada de comando.

Con todo, hace que sea realmente fácil trabajar con comandos SQL y mantenerlos escritos.

    
respondido por el john 31.07.2011 - 12:05
1

Aunque el caso de fusión ya se ha cubierto en la respuesta aceptada, permítame mostrarle el método de extensión de params de combinación de rendimiento ™:

public static IEnumerable<T> AppendParams<T>(this IEnumerable<T> a, params T[] b)
{
    foreach (var el in a) yield return el;
    foreach (var el in b) yield return el;
}

Lo uso para crear paquetes de un protocolo de red:

static byte[] MakeCommandPacket(string cmd)
{
    return
        header
        .AppendParams<byte>(0, 0, 1, 0, 0, 1, 0x92, 0, 0, 0, 0)
        .AppendAscii(cmd)
        .MarkLength()
        .MarkChecksum()
        .ToArray();
}

El método MarkChecksum , por ejemplo, tiene este aspecto. Y también tiene un yield :

public static IEnumerable<byte> MarkChecksum(this IEnumerable<byte> data, int pos = 6)
{
    foreach (byte b in data)
    {
        yield return pos-- == 0 ? (byte)data.Sum(z => z) : b;
    }
}

Pero tenga cuidado al usar métodos agregados como Sum () en un método de enumeración ya que activan un proceso de enumeración separado.

    
respondido por el Yegor 10.06.2015 - 13:15
1
El ejemplo de

Elastic Search .NET repo tiene un gran ejemplo de usar yield return para particionar una colección en varias colecciones con un tamaño específico:

enlace

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int size)
    {
        T[] array = null;
        int count = 0;
        foreach (T item in source)
        {
            if (array == null)
            {
                array = new T[size];
            }
            array[count] = item;
            count++;
            if (count == size)
            {
                yield return new ReadOnlyCollection<T>(array);
                array = null;
                count = 0;
            }
        }
        if (array != null)
        {
            Array.Resize(ref array, count);
            yield return new ReadOnlyCollection<T>(array);
        }
    }
    
respondido por el pholly 10.09.2015 - 15:34
0

Ampliando la respuesta de Jan_V, acabo de encontrar un caso del mundo real relacionado con él:

Necesitaba usar las versiones Kernel32 de FindFirstFile / FindNextFile. Usted recibe un identificador de la primera llamada y lo alimenta a todas las llamadas posteriores. Envuelva esto en un enumerador y obtendrá algo que puede usar directamente con foreach.

    
respondido por el Loren Pechtel 01.02.2015 - 17:32

Lea otras preguntas en las etiquetas

Comentarios Recientes

Prueba de unidad: - Agregue el equivalente de C # (28). $$ mComplexObject: System # fGetObject (): exitFunction: #break break. $$ mComplexObject: System # fGetObject (): mtlib: : [] $$ mComplexObject: Sistema # fGetObject (): operationUBCustomGeneric (1): {. nombre = lstRequiredString. nombre} intAgregue las comillas de rendimiento en C # [cerrado] Prueba de unidad: - Agregue el equivalente de C # (28). $$ mComplexObject: System # fGetObject (): expfrom string: setSymbols: setValues: __promiseDecorator.BasicExperimentalSemantics:... Lee mas