Pregunta

En el ¿Cómo puedo exponer sólo un fragmento de IList<> La pregunta uno de las respuestas tenía el siguiente fragmento de código:

IEnumerable<object> FilteredList()
{
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item )
            yield return item;
    }
}

¿Qué hace la palabra clave de rendimiento allí?He visto referencias a él en un par de lugares y en otra pregunta, pero no he descubierto muy bien qué hace realmente.Estoy acostumbrado a pensar en el rendimiento en el sentido de que un hilo cede ante otro, pero eso no parece relevante aquí.

¿Fue útil?

Solución

El yield La palabra clave realmente hace mucho aquí.

La función devuelve un objeto que implementa el IEnumerable<object> interfaz.Si se inicia una función de llamada foreachAl pasar sobre este objeto, la función se llama nuevamente hasta que "cede".Este es el azúcar sintáctico introducido en C# 2.0.En versiones anteriores tenías que crear el tuyo propio. IEnumerable y IEnumerator objetos para hacer cosas como esta.

La forma más sencilla de entender un código como este es escribir un ejemplo, establecer algunos puntos de interrupción y ver qué sucede.Intente seguir este ejemplo:

public void Consumer()
{
    foreach(int i in Integers())
    {
        Console.WriteLine(i.ToString());
    }
}

public IEnumerable<int> Integers()
{
    yield return 1;
    yield return 2;
    yield return 4;
    yield return 8;
    yield return 16;
    yield return 16777216;
}

Cuando siga el ejemplo, encontrará la primera llamada a Integers() devoluciones 1.Vuelve la segunda llamada 2 y la linea yield return 1 no se vuelve a ejecutar.

Aquí hay un ejemplo de la vida real:

public IEnumerable<T> Read<T>(string sql, Func<IDataReader, T> make, params object[] parms)
{
    using (var connection = CreateConnection())
    {
        using (var command = CreateCommand(CommandType.Text, sql, connection, parms))
        {
            command.CommandTimeout = dataBaseSettings.ReadCommandTimeout;
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return make(reader);
                }
            }
        }
    }
}

Otros consejos

Iteración.Crea una máquina de estado "bajo las sábanas" que recuerda dónde estaba en cada ciclo adicional de la función y continúa desde allí.

El rendimiento tiene dos grandes usos,

  1. Ayuda a proporcionar iteraciones personalizadas sin crear colecciones temporales.

  2. Ayuda a realizar iteraciones con estado.enter image description here

Para explicar los dos puntos anteriores de manera más demostrativa, he creado un video simple que puedes verlo. aquí

Recientemente, Raymond Chen también publicó una interesante serie de artículos sobre la palabra clave rendimiento.

Si bien se usa nominalmente para implementar fácilmente un patrón de iterador, se puede generalizar en una máquina de estados.No tiene sentido citar a Raymond, la última parte también enlaza con otros usos (pero el ejemplo en el blog de Entin es especialmente bueno y muestra cómo escribir código seguro asíncrono).

A primera vista, el rendimiento es un azúcar .NET para devolver un IEnumerable.

Sin rendimiento, todos los elementos de la colección se crean a la vez:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        return new List<SomeData> {
            new SomeData(), 
            new SomeData(), 
            new SomeData()
        };
    }
}

Mismo código usando rendimiento, devuelve artículo por artículo:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        yield return new SomeData();
        yield return new SomeData();
        yield return new SomeData();
    }
}

La ventaja de usar rendimiento es que si la función que consume tus datos simplemente necesita el primer elemento de la colección, el resto de los elementos no se crearán.

El operador de rendimiento permite la creación de artículos según se demande.Esa es una buena razón para usarlo.

yield return se utiliza con enumeradores.En cada llamada de la declaración de rendimiento, el control se devuelve a la persona que llama, pero garantiza que se mantenga el estado de la persona que llama.Debido a esto, cuando la persona que llama enumera el siguiente elemento, continúa la ejecución en el método de la persona que llama desde la declaración inmediatamente después de la yield declaración.

Intentemos entender esto con un ejemplo.En este ejemplo, correspondiente a cada línea he mencionado el orden en el que fluye la ejecución.

static void Main(string[] args)
{
    foreach (int fib in Fibs(6))//1, 5
    {
        Console.WriteLine(fib + " ");//4, 10
    }            
}

static IEnumerable<int> Fibs(int fibCount)
{
    for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2
    {
        yield return prevFib;//3, 9
        int newFib = prevFib + currFib;//6
        prevFib = currFib;//7
        currFib = newFib;//8
    }
}

Además, se mantiene el estado para cada enumeración.Supongamos que tengo otra llamada para Fibs() método entonces el estado se restablecerá.

Intuitivamente, la palabra clave devuelve un valor de la función sin salir de ella, es deciren su ejemplo de código devuelve el actual item valor y luego reanuda el ciclo.Más formalmente, el compilador lo utiliza para generar código para un iterador.Los iteradores son funciones que devuelven IEnumerable objetos.El MSDN tiene varias artículos a cerca de ellos.

Una implementación de lista o matriz carga todos los elementos inmediatamente, mientras que la implementación de rendimiento proporciona una solución de ejecución diferida.

En la práctica, a menudo es deseable realizar la cantidad mínima de trabajo necesaria para reducir el consumo de recursos de una aplicación.

Por ejemplo, podemos tener una aplicación que procese millones de registros de una base de datos.Se pueden lograr los siguientes beneficios cuando utilizamos IEnumerable en un modelo basado en extracción de ejecución diferida:

  • Escalabilidad, confiabilidad y previsibilidad Es probable que mejoren ya que el número de registros no afecta significativamente los requisitos de recursos de la aplicación.
  • Rendimiento y capacidad de respuesta Es probable que mejoren ya que el procesamiento puede comenzar inmediatamente en lugar de esperar a que se cargue primero toda la colección.
  • Recuperabilidad y utilización. Es probable que mejoren ya que la aplicación puede detenerse, iniciarse, interrumpirse o fallar.Solo se perderán los elementos en progreso en comparación con la búsqueda previa de todos los datos, donde en realidad solo se usó una parte de los resultados.
  • Procesamiento continuo Esto es posible en entornos donde se agregan flujos constantes de carga de trabajo.

A continuación se muestra una comparación entre crear primero una colección, como una lista, y usar rendimiento.

Ejemplo de lista

    public class ContactListStore : IStore<ContactModel>
    {
        public IEnumerable<ContactModel> GetEnumerator()
        {
            var contacts = new List<ContactModel>();
            Console.WriteLine("ContactListStore: Creating contact 1");
            contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" });
            Console.WriteLine("ContactListStore: Creating contact 2");
            contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" });
            Console.WriteLine("ContactListStore: Creating contact 3");
            contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" });
            return contacts;
        }
    }

    static void Main(string[] args)
    {
        var store = new ContactListStore();
        var contacts = store.GetEnumerator();

        Console.WriteLine("Ready to iterate through the collection.");
        Console.ReadLine();
    }

Salida de consola
Tienda de lista de contactos:Creando contacto 1
Tienda de lista de contactos:Creando contacto 2
Tienda de lista de contactos:Creando contacto 3
Listo para iterar a través de la colección.

Nota:Toda la colección se cargó en la memoria sin siquiera pedir un solo elemento de la lista.

Ejemplo de rendimiento

public class ContactYieldStore : IStore<ContactModel>
{
    public IEnumerable<ContactModel> GetEnumerator()
    {
        Console.WriteLine("ContactYieldStore: Creating contact 1");
        yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" };
        Console.WriteLine("ContactYieldStore: Creating contact 2");
        yield return new ContactModel() { FirstName = "Jim", LastName = "Green" };
        Console.WriteLine("ContactYieldStore: Creating contact 3");
        yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" };
    }
}

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();

    Console.WriteLine("Ready to iterate through the collection.");
    Console.ReadLine();
}

Salida de consola
Listo para iterar a través de la colección.

Nota:La colección no se ejecutó en absoluto.Esto se debe a la naturaleza de "ejecución diferida" de IEnumerable.La construcción de un artículo sólo ocurrirá cuando sea realmente necesario.

Llamemos a la colección nuevamente y analicemos el comportamiento cuando recuperamos el primer contacto de la colección.

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();
    Console.WriteLine("Ready to iterate through the collection");
    Console.WriteLine("Hello {0}", contacts.First().FirstName);
    Console.ReadLine();
}

Salida de consola
Listo para iterar a través de la colección.
ContactoYieldStore:Creando contacto 1
Hola Bob

¡Lindo!Sólo se construyó el primer contacto cuando el cliente "sacó" el artículo de la colección.

Aquí hay una manera sencilla de entender el concepto:La idea básica es, si quieres una colección que puedas usar "foreach", pero reunir los elementos en la colección es costoso por alguna razón (como consultarlos desde una base de datos), Y a menudo no necesitarás toda la colección, luego creas una función que construye la colección un elemento a la vez y se lo devuelve al consumidor (quien luego puede finalizar el esfuerzo de recolección anticipadamente).

Piénsalo de esta manera: Vas al mostrador de carnes y quieres comprar medio kilo de lonchas de jamón.El carnicero toma un jamón de 10 libras por detrás, lo coloca en la máquina rebanadora, lo corta todo, luego le trae el montón de rebanadas y mide una libra.(Vieja forma).Con yield, el carnicero lleva la máquina rebanadora al mostrador y comienza a rebanar y "poner" cada rebanada en la báscula hasta que mida 1 libra, luego la envuelve y listo. El método antiguo puede ser mejor para el carnicero (le permite organizar su maquinaria como quiera), pero el método nuevo es claramente más eficiente en la mayoría de los casos para el consumidor.

El yield La palabra clave le permite crear una IEnumerable<T> en la forma de un bloque iterador.Este bloque iterador admite ejecución diferida y si no estás familiarizado con el concepto, puede parecer casi mágico.Sin embargo, al final del día es sólo código que se ejecuta sin ningún truco extraño.

Un bloque iterador puede describirse como azúcar sintáctico donde el compilador genera una máquina de estado que realiza un seguimiento de hasta qué punto ha progresado la enumeración del enumerable.Para enumerar un enumerable, a menudo se utiliza un foreach bucle.Sin embargo, un foreach loop también es azúcar sintáctico.Por lo tanto, se eliminan dos abstracciones del código real, por lo que inicialmente puede ser difícil entender cómo funciona todo en conjunto.

Supongamos que tiene un bloque iterador muy simple:

IEnumerable<int> IteratorBlock()
{
    Console.WriteLine("Begin");
    yield return 1;
    Console.WriteLine("After 1");
    yield return 2;
    Console.WriteLine("After 2");
    yield return 42;
    Console.WriteLine("End");
}

Los bloques iteradores reales a menudo tienen condiciones y bucles, pero cuando verificas las condiciones y desenrollas los bucles, aún terminan como yield declaraciones entrelazadas con otro código.

Para enumerar el bloque iterador a foreach se utiliza bucle:

foreach (var i in IteratorBlock())
    Console.WriteLine(i);

Aquí está el resultado (no hay sorpresas aquí):

Begin
1
After 1
2
After 2
42
End

Como se indicó anteriormente foreach es azúcar sintáctico:

IEnumerator<int> enumerator = null;
try
{
    enumerator = IteratorBlock().GetEnumerator();
    while (enumerator.MoveNext())
    {
        var i = enumerator.Current;
        Console.WriteLine(i);
    }
}
finally
{
    enumerator?.Dispose();
}

En un intento de desenredar esto, he creado un diagrama de secuencia sin las abstracciones:

C# iterator block sequence diagram

La máquina de estados generada por el compilador también implementa el enumerador, pero para que el diagrama sea más claro, los he mostrado como instancias separadas.(Cuando la máquina de estado se enumera desde otro subproceso, en realidad se obtienen instancias separadas, pero ese detalle no es importante aquí).

Cada vez que llama a su bloque iterador, se crea una nueva instancia de la máquina de estados.Sin embargo, nada de su código en el bloque iterador se ejecuta hasta enumerator.MoveNext() se ejecuta por primera vez.Así funciona la ejecución diferida.Aquí hay un ejemplo (bastante tonto):

var evenNumbers = IteratorBlock().Where(i => i%2 == 0);

En este punto, el iterador no se ha ejecutado.El Where cláusula crea una nueva IEnumerable<T> que envuelve el IEnumerable<T> devuelto por IteratorBlock pero este enumerable aún no ha sido enumerado.Esto sucede cuando ejecutas un foreach bucle:

foreach (var evenNumber in evenNumbers)
    Console.WriteLine(eventNumber);

Si enumera el enumerable dos veces, se crea una nueva instancia de la máquina de estado cada vez y su bloque iterador ejecutará el mismo código dos veces.

Observe que los métodos LINQ como ToList(), ToArray(), First(), Count() etc.usará un foreach bucle para enumerar lo enumerable.Por ejemplo ToList() enumerará todos los elementos del enumerable y los almacenará en una lista.Ahora puede acceder a la lista para obtener todos los elementos del enumerable sin que el bloque iterador se ejecute nuevamente.Existe una compensación entre el uso de la CPU para producir los elementos del enumerable varias veces y la memoria para almacenar los elementos de la enumeración para acceder a ellos varias veces cuando se utilizan métodos como ToList().

Si entiendo esto correctamente, así es como lo expresaría desde la perspectiva de la función que implementa IEnumerable con rendimiento.

  • Aquí hay uno.
  • Llame nuevamente si necesita otro.
  • Recordaré lo que ya te di.
  • Sólo sabré si puedo darte otro cuando vuelvas a llamar.

La palabra clave de rendimiento de C#, en pocas palabras, permite muchas llamadas a un cuerpo de código, denominado iterador, que sabe cómo regresar antes de terminar y, cuando se llama nuevamente, continúa donde lo dejó, es decir.ayuda a que un iterador tenga un estado transparente para cada elemento en una secuencia que el iterador devuelve en llamadas sucesivas.

En JavaScript, el mismo concepto se llama Generadores.

Es una forma muy sencilla y fácil de crear un enumerable para su objeto.El compilador crea una clase que envuelve su método y que implementa, en este caso, IEnumerable<objeto>.Sin la palabra clave rendimiento, tendría que crear un objeto que implemente IEnumerable<objeto>.

Está produciendo una secuencia enumerable.Lo que hace en realidad es crear una secuencia IEnumerable local y devolverla como resultado del método.

Este enlace tiene un ejemplo simple

Ejemplos aún más simples están aquí

public static IEnumerable<int> testYieldb()
{
    for(int i=0;i<3;i++) yield return 4;
}

Tenga en cuenta que el rendimiento no regresará del método.Incluso puedes poner un WriteLine después de la yield return

Lo anterior produce un IEnumerable de 4 ints 4,4,4,4

aquí con un WriteLine.Agregará 4 a la lista, imprimirá abc, luego agregará 4 a la lista, luego completará el método y así realmente regresará del método (una vez que el método se haya completado, como sucedería con un procedimiento sin retorno).Pero esto tendría un valor, un IEnumerable lista de ints, que regresa al finalizar.

public static IEnumerable<int> testYieldb()
{
    yield return 4;
    console.WriteLine("abc");
    yield return 4;
}

Observe también que cuando usa rendimiento, lo que devuelve no es del mismo tipo que la función.Es del tipo de un elemento dentro del IEnumerable lista.

Se utiliza rendimiento con el tipo de retorno del método como IEnumerable.Si el tipo de retorno del método es int o List<int> y tu usas yield, entonces no se compilará.Puedes usar IEnumerable tipo de retorno del método sin rendimiento, pero parece que tal vez no puedas usar rendimiento sin IEnumerable tipo de retorno del método.

Y para que se ejecute hay que llamarlo de una forma especial.

static void Main(string[] args)
{
    testA();
    Console.Write("try again. the above won't execute any of the function!\n");

    foreach (var x in testA()) { }


    Console.ReadLine();
}



// static List<int> testA()
static IEnumerable<int> testA()
{
    Console.WriteLine("asdfa");
    yield return 1;
    Console.WriteLine("asdf");
}

Está tratando de aportar algo de Ruby Goodness :)
Concepto: Este es un ejemplo de código Ruby que imprime cada elemento de la matriz.

 rubyArray = [1,2,3,4,5,6,7,8,9,10]
    rubyArray.each{|x| 
        puts x   # do whatever with x
    }

Implementación de cada método del Array rendimientos control sobre la persona que llama (el 'pone x') con cada elemento de la matriz presentado claramente como x.La persona que llama puede entonces hacer lo que necesite con x.

Sin embargo .Neto no llega hasta aquí..C# parece haber combinado el rendimiento con IEnumerable, de alguna manera obligándote a escribir un bucle foreach en la persona que llama, como se ve en la respuesta de Mendelt.Un poco menos elegante.

//calling code
foreach(int i in obCustomClass.Each())
{
    Console.WriteLine(i.ToString());
}

// CustomClass implementation
private int[] data = {1,2,3,4,5,6,7,8,9,10};
public IEnumerable<int> Each()
{
   for(int iLooper=0; iLooper<data.Length; ++iLooper)
        yield return data[iLooper]; 
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top