index - C#foreach vs funcional cada
jstl foreach list (8)
¿Cuál de estos prefieres?
foreach(var zombie in zombies)
{
zombie.ShuffleTowardsSurvivors();
zombie.EatNearbyBrains();
}
o
zombies.Each(zombie => {
zombie.ShuffleTowardsSurvivors();
zombie.EatNearbyBrains();
});
Aquí está haciendo algunas cosas muy imperativas como escribir una declaración en lugar de una expresión (como es de suponer que Each
método no devuelve ningún valor) y el estado de mutación (cuál solo se puede asumir que hacen los métodos, ya que también parecen no devolver ningún valor) está intentando pasarlos como "programación funcional" al pasar una colección de declaraciones como delegado. Este código apenas podría estar más lejos de los ideales y los modismos de la programación funcional, así que ¿por qué tratar de disfrazarlo como tal?
Por mucho que me gusten los lenguajes de múltiples paradigmas como C #, creo que son más fáciles de entender y mantener cuando los paradigmas se mezclan en un nivel superior (por ejemplo, un método completo escrito en un estilo funcional o imperativo) en lugar de cuando se utilizan varios paradigmas. Se mezclan dentro de una sola declaración o expresión.
Si está escribiendo un código imperativo, sea honesto al respecto y use un bucle. No hay nada de qué avergonzarse. El código imperativo no es algo intrínsecamente malo.
Creo que los métodos de extensión son geniales, pero creo que las funciones de break
y editar y continuar son más geniales.
El primero. Es parte del lenguaje por una razón.
Personalmente, solo usaría el segundo enfoque funcional para controlar el flujo si hay una buena razón para hacerlo, como usar Parallel.ForEach
en .NET 4. Tiene muchas desventajas, entre ellas:
- Es mas lento Se va a introducir una invocación de delegado en cada elemento, al igual que hiciste para cada
foreach (..) { myDelegate(); }
foreach (..) { myDelegate(); }
- No es estándar, por lo que será más difícil de entender para la mayoría de los desarrolladores
- Si cierras sobre cualquier local, vas a forzar al compilador a hacer un cierre. Esto puede llevar a problemas extraños si hay hilos involucrados, además agrega una hinchazón completamente innecesaria al ensamblaje.
No veo ninguna razón para escribir su propia sintaxis para una construcción de control de flujo que ya existe en el lenguaje.
La versión lamda en realidad no es más lenta. Acabo de hacer una prueba rápida y la versión del delegado es un 30% más rápida.
Aquí está el código:
class Blah {
public void DoStuff() {
}
}
List<Blah> blahs = new List<Blah>();
DateTime start = DateTime.Now;
for(int i = 0; i < 30000000; i++) {
blahs.Add(new Blah());
}
TimeSpan elapsed = (DateTime.Now - start);
Console.WriteLine(string.Format(System.Globalization.CultureInfo.CurrentCulture, "Allocation - {0:00}:{1:00}:{2:00}.{3:000}",
elapsed.Hours,
elapsed.Minutes,
elapsed.Seconds,
elapsed.Milliseconds));
start = DateTime.Now;
foreach(var bl in blahs) {
bl.DoStuff();
}
elapsed = (DateTime.Now - start);
Console.WriteLine(string.Format(System.Globalization.CultureInfo.CurrentCulture, "foreach - {0:00}:{1:00}:{2:00}.{3:000}",
elapsed.Hours,
elapsed.Minutes,
elapsed.Seconds,
elapsed.Milliseconds));
start = DateTime.Now;
blahs.ForEach(bl=>bl.DoStuff());
elapsed = (DateTime.Now - start);
Console.WriteLine(string.Format(System.Globalization.CultureInfo.CurrentCulture, "lambda - {0:00}:{1:00}:{2:00}.{3:000}",
elapsed.Hours,
elapsed.Minutes,
elapsed.Seconds,
elapsed.Milliseconds));
OK, así que he hecho más pruebas y aquí están los resultados.
El orden de la ejecución (forach, lambda o lambda, foreach) no hizo mucha diferencia, la versión lambda fue aún más rápida:
foreach - 00:00:00.561 lambda - 00:00:00.389 lambda - 00:00:00.317 foreach - 00:00:00.337
La diferencia en el rendimiento es mucho menor para las matrices de clases. Aquí están los números para Blah [30000000]:
lambda - 00:00:00.317 foreach - 00:00:00.337
Aquí está la misma prueba pero Blah es una estructura:
Blah[] version lambda - 00:00:00.676 foreach - 00:00:00.437 List version: lambda - 00:00:00.461 foreach - 00:00:00.391
Compilación optimizada, Blah es una estructura que usa una matriz.
lambda - 00:00:00.426 foreach - 00:00:00.079
Conclusión: no hay una respuesta general para el desempeño de foreach vs lambda. La respuesta es: Depende . Here hay una prueba más científica para la List<T>
. Por lo que puedo decir es bastante eficiente. Si está realmente preocupado por el uso del rendimiento for(int i...
loop. Para recorrer una colección de mil registros de clientes (ejemplo), realmente no importa mucho.
En lo que respecta a decidir qué versión usar, pondría un impacto potencial de rendimiento para la versión lambda en la parte inferior.
Conclusión # 2 T[]
(donde T es un tipo de valor) para cada bucle es aproximadamente 5 veces más rápido para esta prueba en una compilación optimizada. Esa es la única diferencia significativa entre un Debug y una versión de lanzamiento. Así que ahí va, para matrices de tipos de valor que se usan para cada persona, todo lo demás, no importa.
Pensaría que la segunda forma sería más difícil de optimizar, ya que no hay manera de que el compilador desenrolle el bucle de manera diferente para esta llamada, como lo hace para la llamada de cualquier otro usuario al método Each.
Como se lo pedí, lo elaboraré. La implementación del método es bastante probable que se compile por separado del código que lo invoca. Esto significa que el compilador no sabe exactamente cuántos bucles tendrá que realizar.
Si utiliza el formulario "foreach", entonces esa información puede estar disponible para el compilador cuando esté creando el código para el bucle (es posible que tampoco esté disponible, en cuyo caso no hay diferencia).
Por ejemplo, si el compilador sabe (de un código anterior en el mismo archivo) que la lista tiene exactamente 20 elementos, puede reemplazar todo el bucle con 20 referencias.
Sin embargo, cuando el compilador crea un código para el método "Cada" desactivado en su archivo fuente, no tiene idea de cuán grande será la lista de la persona que llama. Tiene que soportar cualquier tamaño. Lo mejor que puede hacer es tratar de encontrar algún tipo de desenrollamiento óptimo para su CPU, y agregar código extra para hacer un bucle a través de eso y hacer un bucle adecuado si es demasiado pequeño para el desenrollado. Para un bucle pequeño típico, esto podría incluso ser más lento . Por supuesto, para los bucles pequeños no te importa tanto ... a menos que estén dentro de un gran bucle.
Como mencionó otro póster, esto es (y debería ser) una preocupación secundaria. Lo importante es que es más fácil de leer y / o mantener, pero no veo una gran diferencia allí.
Segunda forma.
En mi opinión, mientras menos construcciones de lenguaje y palabras clave tenga que usar, mejor. C # tiene suficiente basura extraña en ella como es.
Generalmente cuanto menos tienes que escribir, mejor. En serio, ¿cómo es posible que no quieras usar "var" en situaciones como esta? Seguramente, si su único objetivo fuera explícito, seguiría usando la notación húngara ... tiene un IDE que le proporciona información de tipo cada vez que se desplaza sobre ... o, por supuesto, Ctrl + Q si está usando Resharper ... .
@TED Las implicaciones de rendimiento de una invocación de delegado son una preocupación secundaria. Si está haciendo esto con mil términos, ejecute el trazo de puntos y vea si no es aceptable.
@Reed Copsey: no es estándar, si un desarrollador no puede averiguar qué está haciendo ".Each", entonces tiene más problemas, jeje. Hackear el lenguaje para hacerlo más agradable es una de las grandes alegrías de la programación.
Yo tampoco prefiero, debido a lo que considero un uso innecesario de ''var''. Yo escribiría es como:
foreach(Zombie zombie in zombies){
}
Pero en cuanto a Functional o foreach, para mí definitivamente prefiero foreach, porque no parece haber una buena razón para esto último.
Esta pregunta contiene una discusión útil, así como un enlace a una publicación del blog de MSDN, sobre los aspectos filosóficos del tema.