En la pregunta Cómo puedo exponer solo un fragmento de IList <> , una 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í? Lo he visto referenciado en un par de lugares, y otra pregunta, pero aún no he descubierto lo que realmente hace. Estoy acostumbrado a pensar en el rendimiento en el sentido de que un hilo cede a otro, pero eso no parece relevante aquí.
Respuestas:
La
yield
palabra clave realmente hace mucho aquí.La función devuelve un objeto que implementa la
IEnumerable<object>
interfaz. Si una función de llamada comienzaforeach
sobre este objeto, la función se llama nuevamente hasta que "ceda". Este es el azúcar sintáctico introducido en C # 2.0 . En versiones anteriores, tenía que crear los suyosIEnumerable
y losIEnumerator
objetos para hacer cosas como esta.La forma más fácil de entender un código como este es escribir un ejemplo, establecer algunos puntos de interrupción y ver qué sucede. Intenta seguir este ejemplo:
Cuando pase por el ejemplo, encontrará la primera llamada a
Integers()
devoluciones1
. La segunda llamada regresa2
y la líneayield return 1
no se ejecuta nuevamente.Aquí hay un ejemplo de la vida real:
fuente
yield break;
cuando no desea devolver más artículos.yield
No es una palabra clave. Si fuese entonces no podía usar rendimiento como un identificador como enint yield = 500;
'If a calling function starts foreach-ing over this object the function is called again until it "yields"'
. no me suena bien Siempre pensé en la palabra clave c # yield en el contexto de "el cultivo produce una cosecha abundante", en lugar de "el automóvil rinde al peatón".Iteración. Crea una máquina de estado "debajo de las cubiertas" que recuerda dónde estaba en cada ciclo adicional de la función y comienza desde allí.
fuente
El rendimiento tiene dos grandes usos,
Ayuda a proporcionar iteraciones personalizadas sin crear colecciones temporales.
Ayuda a hacer iteraciones con estado.
Para explicar los dos puntos anteriores de manera más demostrativa, he creado un video simple que puedes ver aquí.
fuente
yield
. Artículo del proyecto de código de @ ShivprasadKoirala ¿Cuál es el uso de C # Yield? de la misma explicación también es una buena fuenteyield
es una forma "rápida" de crear un IEnumerator personalizado (en lugar de que una clase implemente la interfaz IEnumerator).Recientemente, Raymond Chen también publicó una interesante serie de artículos sobre la palabra clave de rendimiento.
Si bien se usa nominalmente para implementar fácilmente un patrón iterador, pero se puede generalizar en una máquina de estado. 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, mostrando cómo escribir código seguro asíncrono).
fuente
A primera vista, el rendimiento del rendimiento es un azúcar .NET para devolver un IEnumerable .
Sin rendimiento, todos los elementos de la colección se crean a la vez:
Mismo código usando el rendimiento, devuelve artículo por artículo:
La ventaja de usar el rendimiento es que si la función que consume sus 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 solicite. Esa es una buena razón para usarlo.
fuente
yield return
se usa con enumeradores. En cada declaración de llamada de rendimiento, el control se devuelve a la persona que llama, pero asegura 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 llamado desde la declaración inmediatamente después de layield
declaración.Tratemos de entender esto con un ejemplo. En este ejemplo, correspondiente a cada línea, he mencionado el orden en que fluye la ejecución.
Además, el estado se mantiene para cada enumeración. Supongamos que tengo otra llamada al
Fibs()
método y luego el estado se restablecerá.fuente
Intuitivamente, la palabra clave devuelve un valor de la función sin abandonarla, es decir, en su ejemplo de código, devuelve el
item
valor actual y luego reanuda el ciclo. Más formalmente, el compilador lo utiliza para generar código para un iterador . Los iteradores son funciones que devuelvenIEnumerable
objetos. El MSDN tiene varios artículos sobre ellos.fuente
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 según sea necesario 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. Los siguientes beneficios se pueden lograr cuando usamos IEnumerable en un modelo basado en extracción de ejecución diferida:
Aquí hay una comparación entre construir una colección primero, como una lista en comparación con el uso de rendimiento.
Ejemplo de lista
Salida de consola
ContactListStore: creación de contacto 1
ContactListStore: creación de contacto 2
ContactListStore: creación de 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 en la lista
Ejemplo de rendimiento
Salida de consola
Lista para iterar por 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 solo ocurrirá cuando sea realmente necesario.
Volvamos a llamar a la colección y anulemos el comportamiento cuando busquemos el primer contacto de la colección.
Salida de consola
Lista para iterar a través de la colección
ContactYieldStore: creación de contacto 1
Hola Bob
¡Agradable! Solo se construyó el primer contacto cuando el cliente "sacó" el artículo de la colección.
fuente
Aquí hay una manera simple de entender el concepto: la idea básica es, si desea una colección en la que pueda 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á la colección completa, luego crea una función que construye la colección un elemento a la vez y la devuelve al consumidor (que luego puede finalizar el esfuerzo de recolección antes).Piénsalo de esta manera: vas al mostrador de carne y quieres comprar una libra de jamón rebanado. El carnicero lleva un jamón de 10 libras a la parte posterior, lo pone en la máquina rebanadora, lo corta todo, luego le devuelve el montón de rebanadas y mide una libra de él. (Vieja forma). Con
yield
, el carnicero lleva la máquina rebanadora al mostrador, y comienza a rebanar y "ceder" cada rebanada en la balanza hasta que mida 1 libra, luego la envuelve y listo. El Old Way puede ser mejor para el carnicero (le permite organizar su maquinaria de la manera que quiera), pero el New Way es claramente más eficiente en la mayoría de los casos para el consumidor.fuente
La
yield
palabra clave le permite crear unIEnumerable<T>
formulario en un bloque iterador . Este bloque iterador admite la ejecución diferida y, si no está familiarizado con el concepto, puede parecer casi mágico. Sin embargo, al final del día, solo el código se ejecuta sin ningún truco extraño.Un bloque iterador puede describirse como azúcar sintáctico en el que el compilador genera una máquina de estados que realiza un seguimiento de hasta qué punto ha progresado la enumeración de lo enumerable. Para enumerar un enumerable, a menudo usa un
foreach
bucle. Sin embargo, unforeach
bucle también es azúcar sintáctico. Entonces, son dos abstracciones eliminadas del código real, por lo que inicialmente podría ser difícil entender cómo funciona todo junto.Suponga que tiene un bloque iterador muy simple:
Los bloques iteradores reales a menudo tienen condiciones y bucles, pero cuando verifica las condiciones y desenrolla los bucles, terminan siendo
yield
declaraciones intercaladas con otro código.Para enumerar el iterador, bloquee un
foreach
se usa bucle:Aquí está la salida (no hay sorpresas aquí):
Como se indicó anteriormente
foreach
es el azúcar sintáctico:En un intento de desenredar esto, he creado un diagrama de secuencia con las abstracciones eliminadas:
La máquina de estado 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 estado. Sin embargo, ninguno de sus códigos en el bloque iterador se ejecuta hasta que se
enumerator.MoveNext()
ejecute por primera vez. Así es como funciona la ejecución diferida. Aquí hay un ejemplo (bastante tonto):En este punto, el iterador no se ha ejecutado. La
Where
cláusula crea un nuevoIEnumerable<T>
que envuelve elIEnumerable<T>
devuelto porIteratorBlock
pero este enumerable aún no se ha enumerado. Esto sucede cuando ejecuta unforeach
bucle: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.
Tenga en cuenta que los métodos de LINQ gusta
ToList()
,ToArray()
,First()
,Count()
etc. va a utilizar unforeach
bucle para enumerar la enumerable. Por ejemploToList()
, enumerará todos los elementos enumerables 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 CPU para producir los elementos de los enumerables múltiples veces y la memoria para almacenar los elementos de la enumeración para acceder a ellos varias veces cuando se utilizan métodos comoToList()
.fuente
Si entiendo esto correctamente, así es como lo expresaría desde la perspectiva de la función que implementa IEnumerable con rendimiento.
fuente
La palabra clave de rendimiento C #, en pocas palabras, permite muchas llamadas a un cuerpo de código, conocido como iterador, que sabe cómo regresar antes de que se haga y, cuando se llama nuevamente, continúa donde se quedó, es decir, ayuda a un iterador se vuelve transparente con estado por cada elemento en una secuencia que el iterador devuelve en llamadas sucesivas.
En JavaScript, el mismo concepto se llama Generadores.
fuente
Es una manera muy simple 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 <object>. Sin la palabra clave de rendimiento, tendría que crear un objeto que implemente IEnumerable <object>.
fuente
Está produciendo una secuencia enumerable. Lo que hace es crear una secuencia local IEnumerable y devolverla como resultado de un método
fuente
Este enlace tiene un ejemplo simple
Incluso ejemplos más simples están aquí
Tenga en cuenta que el rendimiento no regresará del método. Incluso puedes poner un
WriteLine
después delyield 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í volverá realmente 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, unaIEnumerable
lista deint
s, que devuelve al finalizar.Observe también que cuando usa el rendimiento, lo que está devolviendo no es del mismo tipo que la función. Es del tipo de un elemento dentro de la
IEnumerable
lista.Utiliza el rendimiento con el tipo de retorno del método como
IEnumerable
. Si el tipo de retorno del método esint
oList<int>
y usted usayield
, entonces no se compilará. Puede usar elIEnumerable
tipo de retorno del método sin rendimiento, pero parece que tal vez no pueda usar el rendimiento sin elIEnumerable
tipo de retorno del método.Y para que se ejecute, debe llamarlo de una manera especial.
fuente
public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { yield return t; }
ypublic static IEnumerable<TResult> testYieldc<TResult>(TResult t) { return new List<TResult>(); }
yield return
bien (aparte de lo simple que mencioné), y no lo he usado mucho y no sé mucho sobre sus usos, no creo que este sea el aceptado.Un punto importante sobre la palabra clave Yield es Lazy Execution . Ahora lo que quiero decir con Lazy Execution es ejecutar cuando sea necesario. Una mejor manera de decirlo es dando un ejemplo
Ejemplo: no se utiliza Yield, es decir, no hay ejecución diferida.
Ejemplo: uso de rendimiento, es decir, ejecución diferida.
Ahora cuando llamo a ambos métodos.
notará que listItems tendrá 5 elementos dentro (desplace el mouse sobre listItems durante la depuración). Mientras que yieldItems solo tendrá una referencia al método y no a los elementos. Eso significa que no ha ejecutado el proceso de obtener elementos dentro del método. Una forma muy eficiente de obtener datos solo cuando es necesario. La implementación real del rendimiento se puede ver en ORM como Entity Framework y NHibernate, etc.
fuente
Está tratando de aportar algo de Ruby Goodness :)
Concepto: este es un ejemplo de código Ruby que imprime cada elemento de la matriz
La implementación de cada método de la matriz le da el control a la persona que llama (el 'pone x') con cada elemento de la matriz perfectamente presentado como x. La persona que llama puede hacer lo que tenga que hacer con x.
Sin embargo, .Net no llega hasta aquí ... C # parece haber unido el rendimiento con IEnumerable, de tal forma que te obliga a escribir un bucle foreach en la persona que llama como se ve en la respuesta de Mendelt. Poco menos elegante.
fuente
yield
está asociadoIEnumerable
y C # carece del concepto de Ruby de "bloque". Pero C # tiene lambdas, lo que podría permitir la implementación de unForEach
método, muy similar al de Rubyeach
. Sin embargo, esto no significa que sea una buena idea hacerlo .