La palabra clave de rendimiento es una de esas palabras clave en C # que continúa desconcertando, y nunca he estado seguro de que la estoy usando correctamente.
De los siguientes dos códigos, ¿cuál es el preferido y por qué?
Versión 1: uso del rendimiento
public static IEnumerable<Product> GetAllProducts()
{
using (AdventureWorksEntities db = new AdventureWorksEntities())
{
var products = from product in db.Product
select product;
foreach (Product product in products)
{
yield return product;
}
}
}
Versión 2: devolver la lista
public static IEnumerable<Product> GetAllProducts()
{
using (AdventureWorksEntities db = new AdventureWorksEntities())
{
var products = from product in db.Product
select product;
return products.ToList<Product>();
}
}
c#
yield-return
senfo
fuente
fuente
yield
está atadoIEnumerable<T>
y es amable. De alguna manera es una evaluación perezosayield return
si el código que itera a través de los resultados de leGetAllProducts()
permite al usuario la posibilidad de cancelar prematuramente el procesamiento.Respuestas:
Tiendo a usar el rendimiento-rendimiento cuando calculo el siguiente elemento de la lista (o incluso el siguiente grupo de elementos).
Usando su Versión 2, debe tener la lista completa antes de regresar. Al usar el rendimiento-retorno, realmente solo necesita tener el siguiente artículo antes de regresar.
Entre otras cosas, esto ayuda a distribuir el costo computacional de los cálculos complejos en un marco de tiempo más amplio. Por ejemplo, si la lista está conectada a una GUI y el usuario nunca va a la última página, nunca calcula los elementos finales de la lista.
Otro caso en el que es preferible el rendimiento-rendimiento es si IEnumerable representa un conjunto infinito. Considere la lista de números primos, o una lista infinita de números aleatorios. Nunca puede devolver el IEnumerable completo de una vez, por lo que utiliza el rendimiento-retorno para devolver la lista de forma incremental.
En su ejemplo particular, tiene la lista completa de productos, así que usaría la Versión 2.
fuente
Yield return
parece ser una forma abreviada de escribir su propia clase de iterador personalizado (implementar IEnumerator). Por lo tanto, los beneficios mencionados también se aplican a las clases de iterador personalizadas. De todos modos, ambas construcciones mantienen un estado intermedio. En su forma más simple, se trata de mantener una referencia al objeto actual.Completar una lista temporal es como descargar todo el video, mientras que usarlo
yield
es como transmitir ese video.fuente
Como ejemplo conceptual para comprender cuándo debe usar
yield
, supongamos que el métodoConsumeLoop()
procesa los elementos devueltos / cedidos porProduceList()
:Sin esto
yield
, la llamada aProduceList()
puede tomar mucho tiempo porque debe completar la lista antes de regresar:Usando
yield
, se reorganiza, trabajando "en paralelo":Y, por último, como muchos ya lo han sugerido, debe usar la Versión 2 porque ya tiene la lista completa de todos modos.
fuente
Sé que esta es una vieja pregunta, pero me gustaría ofrecer un ejemplo de cómo la palabra clave de rendimiento se puede usar de manera creativa. Realmente me he beneficiado de esta técnica. Esperemos que esto sea de ayuda para cualquier otra persona que se encuentre con esta pregunta.
Nota: No piense en la palabra clave de rendimiento como simplemente otra forma de crear una colección. Una gran parte del poder del rendimiento viene del hecho de que la ejecución se detiene en su método o propiedad hasta que el código de llamada itera sobre el siguiente valor. Aquí está mi ejemplo:
El uso de la palabra clave de rendimiento (junto con la implementación de las rutinas Caliburn.Micro de Rob Eisenburg ) me permite expresar una llamada asincrónica a un servicio web como este:
Lo que esto hará es encender mi BusyIndicator, llamar al método de inicio de sesión en mi servicio web, establecer mi indicador IsLoggedIn en el valor de retorno y luego volver a apagar el BusyIndicator.
Así es como funciona: IResult tiene un método de ejecución y un evento completado. Caliburn.Micro toma el IEnumerator de la llamada a HandleButtonClick () y lo pasa a un método Coroutine.BeginExecute. El método BeginExecute comienza a iterar a través de los IResults. Cuando se devuelve el primer IResult, la ejecución se detiene dentro de HandleButtonClick (), y BeginExecute () adjunta un controlador de eventos al evento Completed y llama a Execute (). IResult.Execute () puede realizar una tarea sincrónica o asincrónica y dispara el evento Completed cuando se realiza.
LoginResult se parece a esto:
Puede ser útil configurar algo como esto y pasar por la ejecución para ver lo que está sucediendo.
¡Espero que esto ayude a alguien! Realmente disfruté explorando las diferentes formas en que se puede usar el rendimiento.
fuente
yield
de esta manera. Parece una forma elegante de emular el patrón asíncrono / espera (que supongo que se usaría en lugar deyield
si se reescribiera hoy). ¿Ha descubierto que estos usos creativos deyield
han producido rendimientos decrecientes (sin juego de palabras) a lo largo de los años a medida que C # ha evolucionado desde que respondió a esta pregunta? ¿O todavía se te ocurren casos de uso inteligentes y modernizados como este? Y si es así, ¿te importaría compartir otro escenario interesante para nosotros?Esto parecerá una sugerencia extraña, pero aprendí a usar la
yield
palabra clave en C # leyendo una presentación sobre generadores en Python: http://www.dabeaz.com/generators/Generators.pdf de David M. Beazley . No necesitas saber mucho de Python para entender la presentación, no lo hice. Me pareció muy útil para explicar no solo cómo funcionan los generadores, sino también por qué debería importarle.fuente
El rendimiento de rendimiento puede ser muy poderoso para algoritmos en los que necesita iterar a través de millones de objetos. Considere el siguiente ejemplo donde necesita calcular posibles viajes para compartir viajes. Primero generamos posibles viajes:
Luego, repita cada viaje:
Si usa Lista en lugar de rendimiento, necesitará asignar 1 millón de objetos a la memoria (~ 190mb) y este sencillo ejemplo tardará ~ 1400ms en ejecutarse. Sin embargo, si usa el rendimiento, no necesita colocar todos estos objetos temporales en la memoria y obtendrá una velocidad de algoritmo significativamente más rápida: este ejemplo tomará solo ~ 400 ms para ejecutarse sin consumo de memoria.
fuente
yield
funciona debajo de las cubiertas implementando una máquina de estado internamente. Aquí hay una respuesta SO con 3 publicaciones detalladas en el blog de MSDN que explican la implementación con gran detalle. Escrito por Raymond Chen @ MSFTLas dos piezas de código realmente están haciendo dos cosas diferentes. La primera versión atraerá a los miembros cuando los necesite. La segunda versión cargará todos los resultados en la memoria antes de comenzar a hacer algo con ella.
No hay una respuesta correcta o incorrecta a esta. Cuál es preferible solo depende de la situación. Por ejemplo, si hay un límite de tiempo para completar su consulta y necesita hacer algo semi-complicado con los resultados, la segunda versión podría ser preferible. Pero tenga cuidado con los grandes conjuntos de resultados, especialmente si está ejecutando este código en modo de 32 bits. Me han mordido las excepciones de OutOfMemory varias veces al hacer este método.
Sin embargo, lo clave a tener en cuenta es esto: las diferencias están en la eficiencia. Por lo tanto, probablemente debería elegir el que simplifique su código y cambiarlo solo después de la creación de perfiles.
fuente
El rendimiento tiene dos grandes usos
Ayuda a proporcionar iteraciones personalizadas sin crear colecciones temporales. (cargando todos los datos y bucles)
Ayuda a hacer iteraciones con estado. ( transmisión)
A continuación se muestra un video simple que he creado con una demostración completa para respaldar los dos puntos anteriores
http://www.youtube.com/watch?v=4fju3xcm21M
fuente
Esto es lo que Chris Sells cuenta sobre esas declaraciones en el lenguaje de programación C # ;
fuente
F().Any()
, esto volverá después de intentar enumerar solo el primer resultado. En general, no debe confiar en unIEnumerable yield
cambio de estado del programa, ya que en realidad no puede activarseSuponiendo que la clase LINQ de sus productos utiliza un rendimiento similar para enumerar / iterar, la primera versión es más eficiente porque solo produce un valor cada vez que se repite.
El segundo ejemplo es convertir el enumerador / iterador en una lista con el método ToList (). Esto significa que itera manualmente sobre todos los elementos en el enumerador y luego devuelve una lista plana.
fuente
Esto está un poco fuera del punto, pero dado que la pregunta está etiquetada como mejores prácticas, seguiré adelante y arrojaré mis dos centavos. Para este tipo de cosas, prefiero convertirlo en una propiedad:
Claro, es un poco más de placa de caldera, pero el código que usa esto se verá mucho más limpio:
vs
Nota: No haría esto por ningún método que pueda tomar un tiempo para hacer su trabajo.
fuente
¿Y qué hay de esto?
Supongo que esto es mucho más limpio. Sin embargo, no tengo VS2008 a mano para verificar. En cualquier caso, si Products implementa IEnumerable (como parece, se usa en una declaración foreach), lo devolvería directamente.
fuente
Hubiera usado la versión 2 del código en este caso. Como tiene la lista completa de productos disponibles y eso es lo que espera el "consumidor" de esta llamada de método, se le solicitará que envíe la información completa a la persona que llama.
Si la persona que llama de este método requiere "una" información a la vez y el consumo de la siguiente información es bajo demanda, entonces sería beneficioso utilizar el rendimiento de rendimiento, lo que asegurará que el comando de ejecución se devolverá a la persona que llama cuando Una unidad de información está disponible.
Algunos ejemplos en los que se podría utilizar el rendimiento del rendimiento son:
Para responder a sus preguntas, habría usado la versión 2.
fuente
Devuelve la lista directamente. Beneficios:
La lista es reutilizable. (el iterador no es)no es cierto, gracias JonDebería usar el iterador (rendimiento) cuando piense que probablemente no tendrá que iterar hasta el final de la lista, o cuando no tenga fin. Por ejemplo, la llamada del cliente buscará el primer producto que satisfaga algún predicado, puede considerar usar el iterador, aunque ese es un ejemplo artificial, y probablemente haya mejores formas de lograrlo. Básicamente, si sabe de antemano que será necesario calcular la lista completa, simplemente hágalo por adelantado. Si crees que no lo hará, entonces considera usar la versión iteradora.
fuente
La frase clave de retorno de rendimiento se usa para mantener la máquina de estado para una colección particular. Dondequiera que el CLR vea que se usa la frase clave de rendimiento, CLR implementa un patrón Enumerator para ese fragmento de código. Este tipo de implementación ayuda al desarrollador de todo el tipo de plomería que de otro modo tendríamos que hacer en ausencia de la palabra clave.
Suponga que si el desarrollador está filtrando alguna colección, iterando a través de la colección y luego extrayendo esos objetos en alguna nueva colección. Este tipo de fontanería es bastante monótono.
Más información sobre la palabra clave aquí en este artículo .
fuente
El uso del rendimiento es similar a la palabra clave return , excepto que devolverá un generador . Y el objeto generador solo atravesará una vez .
el rendimiento tiene dos beneficios:
Hay otra explicación clara que quizás te ayude.
fuente