2009-03-23 22 views
50

Muchas preguntas están siendo respondidas en Stack   Overflow, con miembros que especifican cómo resolver estos problemas reales de tiempo/mundo usando lambda expressions.Cuándo no utilizar expresiones lambda

¿Estamos abusando de él, y estamos considerando el impacto en el rendimiento del uso de expresiones lambda?

me encontré con un par de artículos que explora el impacto en el rendimiento de lambda vs delegados anónimos vs for/foreach bucles con diferentes resultados

  1. Anonymous Delegates vs Lambda Expressions vs Function Calls Performance
  2. Performance of foreach vs. List.ForEach
  3. .NET/C# Loop Performance Test (FOR, FOREACH, LINQ, & Lambda).
  4. DataTable.Select is faster than LINQ

Cuáles deberían ser los criterios de evaluación de la hora de elegir la solución adecuada? Excepto por la razón obvia de que es un código más conciso y legible cuando se usa lambda.

+2

Le recomiendo que visite www.lambdaexpression.net – Delashmate

+5

dominio @Delashmate asumido por los spammers ahora – ozz

Respuesta

15

Duplicación de código.
Si te encuentras escribiendo la misma función anónima más de una vez, no debería ser una.

+0

Depende de dónde se duplicará el código. Supongamos que debe ordenar una lista usando la misma condición varias veces dentro de un método. Probablemente declararía un lambda en el alcance del método y lo volvería a utilizar. Pero en general, estoy de acuerdo con esto! +1 –

+0

Bueno, no del todo. Si sus tipos son remotamente complejos, especificar el tipo completo de la lambda podría ser mucho más trabajo que duplicarlo varias veces. – MichaelGG

+0

Para eso es 'var'. OTOH, si necesitas pasar o devolver el lambda como parámetro, entonces estoy de acuerdo. –

14

Bueno, cuando hablamos sobre el uso de delegados, no debería haber ninguna diferencia entre los métodos lambda y anónimo: son lo mismo, solo que con una sintaxis diferente. Y los métodos nombrados (utilizados como delegados) también son idénticos desde el punto de vista del tiempo de ejecución. La diferencia es, pues, entre el uso de los delegados, frente a código en línea - es decir

list.ForEach(s=>s.Foo()); 
// vs. 
foreach(var s in list) { s.Foo(); } 

(donde yo esperaría que ésta sea más rápido)

E igualmente, si se trata de nada otra que los objetos en memoria, las lambdas son una de sus herramientas más potentes en términos de mantener la verificación de tipos (en lugar de analizar cadenas todo el tiempo).

Ciertamente, hay casos en que un simple foreach con código será más rápido que la versión LINQ, ya que habrá menos llamadas a hacer, e invoca un costo pequeño pero mensurable. Sin embargo, en muchos casos, el código simplemente no es el cuello de botella, y el código más simple (especialmente para agrupar, etc.) vale mucho más que unos pocos nanosegundos.

Tenga en cuenta también que en .NET 4.0 hay additional Expression nodes para cosas como bucles, comas, etc. El lenguaje no los admite, pero el tiempo de ejecución sí. Menciono esto solo por completitud: ¡ciertamente no digo que deba usar la construcción manual Expression donde foreach haría!

+1

Un poco fuera de tema, pero me refiero a haber escuchado algo en las líneas de List.ForEach (. => ..) para ser más rápido/más eficiente/o algo que usando foreach (..) {..} porque tiene acceso a la matriz interna y no usa enumeradores y tal? Sin embargo, puede estar equivocado ... – Svish

+0

Puede tener tal acceso, pero necesita hacer invocar delegado, por lo que tiene doble filo. Necesitarías un perfil para estar seguro, pero esperaría que 'foreach' fuera marginalmente más rápido. –

+2

Irónicamente, ese no es el caso. Créalo o no, List.ForEach tiene el borde. La invocación de un único delegado es bastante rápida (se aproxima a la misma velocidad que una llamada de interfaz). –

6

Diría que las diferencias de rendimiento son generalmente tan pequeñas (y en el caso de los bucles, obviamente, si mira los resultados del segundo artículo (por cierto, Jon Skeet tiene un artículo similar here)) que casi nunca debe elegir una solución solo por razones de rendimiento, a menos que esté escribiendo una pieza de software donde el rendimiento es absolutamente el requisito número uno no funcional y realmente tiene que hacer micro-optimizaciones.

¿Cuándo elegir qué? Supongo que depende de la situación pero también de la persona. Solo como un ejemplo, algunas personas prefieren Lista.Foreach sobre un ciclo foreach normal. I personalmente prefiero este último, ya que generalmente es más legible, pero ¿quién soy para argumentar en contra de esto?

+0

tampoco argumento de mí :) –

+0

I * siempre * uso 'foreach' cuando el resultado es para efectos secundarios. Lambdas por toda la diversión de LINQ, por supuesto. –

4

Reglas de oro:

  1. escribir el código a ser natural y fácil de leer.
  2. Evite las duplicaciones de código (las expresiones lambda pueden requerir un poco de diligencia extra).
  3. Optimice solo cuando haya un problema, y ​​solo con datos para respaldar el problema.
+2

"Hazlo correcto, acláralo, hazlo conciso, hazlo rápido. En ese orden" (Wes Dyer http://blogs.msdn.com/wesdyer/archive/2007/03/01/immutability-purity-and -referential-transparency.aspx) – Benjol

+0

Eso es muy cierto. La primera parte a menudo se habla menos, "corregirlo". De hecho, por lo general, los primeros tres caen bien. Si lo hace correcto, es bastante legible, que a su vez será bastante conciso. – nawfal

2

Si necesita recursion, no use lambdas, or you'll end up getting very distracted!

+0

Comparar con F #: "dejar rec y f x = f (y f) x" :) – MichaelGG

+0

No estoy de acuerdo. La recursividad no está clara en ningún enfoque. –

+3

@Jerry Nixon - La recursividad a veces es mucho más clara que la alternativa. Intente trabajar a través de http://mitpress.mit.edu/sicp/ –

31

Aunque me centraré en el punto uno, empiezo por dar mis 2 centavos en todo el tema del rendimiento. A menos que las diferencias sean grandes o el uso sea intenso, generalmente no me preocupo por los microsegundos que cuando se agregan no representan una diferencia visible para el usuario. Enfatizo que solo no me importa cuando considero métodos llamados no intensivos. Donde sí tengo consideraciones de rendimiento especiales es en la forma en que diseño la aplicación en sí. Me importa el almacenamiento en caché, sobre el uso de hilos, sobre formas inteligentes de llamar a métodos (ya sea para hacer varias llamadas o para intentar hacer una sola llamada), si unir conexiones o no, etc., etc. De hecho, suelo donar no se centran en el rendimiento en bruto, sino en la escalabilidad. No me importa si funciona mejor con una pequeña fracción de nanosegundo para un solo usuario, pero me preocupa mucho poder cargar el sistema con grandes cantidades de usuarios simultáneos sin notar el impacto.

Habiendo dicho eso, aquí va mi opinión sobre el punto 1. Me encantan los métodos anónimos. Me dan una gran flexibilidad y elegancia de código. La otra gran característica de los métodos anónimos es que me permiten usar directamente las variables locales desde el método contenedor (desde una perspectiva C#, no desde una perspectiva IL, por supuesto). Me ahorran mucho código a menudo. ¿Cuándo uso métodos anónimos? Solo una vez el pedazo de código que necesito no es necesario en otro lado. Si se usa en dos lugares diferentes, no me gusta copiar y pegar como técnica de reutilización, así que usaré un simple delegado. Entonces, al igual que respondió shoosh, no es bueno tener duplicación de código. En teoría, no hay diferencias de rendimiento ya que los anónimos son trucos de C#, no cosas de IL.

La mayor parte de lo que pienso sobre los métodos anónimos se aplica a las expresiones lambda, ya que este último se puede usar como una sintaxis compacta para representar métodos anónimos. Vamos a suponer que el siguiente método:

public static void DoSomethingMethod(string[] names, Func<string, bool> myExpression) 
{ 
    Console.WriteLine("Lambda used to represent an anonymous method"); 
    foreach (var item in names) 
    { 
     if (myExpression(item)) 
      Console.WriteLine("Found {0}", item); 
    } 
} 

Recibe una matriz de cadenas y para cada uno de ellos, se llamará al método se le ha pasado. Si ese método devuelve verdadero, dirá "Encontrado ...". Puede llamar a este método de la siguiente manera:

string[] names = {"Alice", "Bob", "Charles"}; 
DoSomethingMethod(names, delegate(string p) { return p == "Alice"; }); 

embargo, también se puede llamar de la siguiente manera:

DoSomethingMethod(names, p => p == "Alice"); 

No hay diferencia en IL entre los dos, es que el uso de la La expresión Lambda es mucho más legible. Una vez más, no hay impacto en el rendimiento ya que estos son todos trucos del compilador de C# (no trucos del compilador JIT). De la misma manera que no me parece que usemos demasiado los métodos anónimos, no creo que exageremos las expresiones de Lambda para representar métodos anónimos. Por supuesto, la misma lógica se aplica al código repetido: No hagas lambdas, usa delegados regulares. Existen otras restricciones que lo llevan de regreso a métodos anónimos o simples delegados, como pasar o rechazar argumentos.

Las otras cosas agradables sobre las expresiones Lambda es que la misma sintaxis exacta no necesita representar un método anónimo. Las expresiones Lambda también pueden representar ... adivinaste, expresiones. Tome el siguiente ejemplo:

public static void DoSomethingExpression(string[] names, System.Linq.Expressions.Expression<Func<string, bool>> myExpression) 
{ 
    Console.WriteLine("Lambda used to represent an expression"); 
    BinaryExpression bExpr = myExpression.Body as BinaryExpression; 
    if (bExpr == null) 
     return; 
    Console.WriteLine("It is a binary expression"); 
    Console.WriteLine("The node type is {0}", bExpr.NodeType.ToString()); 
    Console.WriteLine("The left side is {0}", bExpr.Left.NodeType.ToString()); 
    Console.WriteLine("The right side is {0}", bExpr.Right.NodeType.ToString()); 
    if (bExpr.Right.NodeType == ExpressionType.Constant) 
    { 
     ConstantExpression right = (ConstantExpression)bExpr.Right; 
     Console.WriteLine("The value of the right side is {0}", right.Value.ToString()); 
    } 
} 

Observe la firma ligeramente diferente. El segundo parámetro recibe una expresión y no un delegado. La forma de llamar a este método sería:

DoSomethingExpression(names, p => p == "Alice"); 

¿Qué es exactamente la misma que la llamada que hicimos al crear un método anónimo con una lambda. La diferencia aquí es que no estamos creando un método anónimo, sino creando un árbol de expresiones. Es debido a estos árboles de expresiones que podemos traducir expresiones lambda a SQL, que es lo que Linq 2 SQL hace, por ejemplo, en lugar de ejecutar cosas en el motor para cada cláusula, como Where, Select, etc. Lo bueno es que la sintaxis de llamada es la misma ya sea que esté creando un método anónimo o enviando una expresión.

+3

+1 Todo lo bueno. Una pequeña corrección: la sobrecarga de instanciar invocación y GC-ing un método anónimo se mide en nanosegundos, ¡no en microsegundos! –

+0

Sí, por supuesto, ¡gracias! lolol –

4

En cualquier momento el lambda simplemente pasa sus argumentos directamente a otra función. No cree un lambda para la aplicación de funciones.

Ejemplo:

var coll = new ObservableCollection<int>(); 
myInts.ForEach(x => coll.Add(x)) 

es más agradable como:

var coll = new ObservableCollection<int>(); 
myInts.ForEach(coll.Add) 

La principal excepción es la que C# 's inferencia de tipos falla por cualquier razón (y hay un montón de veces que es verdad).

23

Mi respuesta no será popular.

Creo que el 99% de Lambda siempre es la mejor opción por tres razones.

En primer lugar, ABSOLUTAMENTE no hay nada de malo en asumir que sus desarrolladores son inteligentes. Otras respuestas tienen una premisa subyacente de que cada desarrollador, excepto usted, es estúpido. No tan.

En segundo lugar, Lamdas (et al) son una sintaxis moderna, y mañana serán más comunes de lo que son hoy en día. El código de su proyecto debe fluir de las convenciones actuales y emergentes.

En tercer lugar, escribir código "a la antigua usanza" puede parecerle más fácil, pero no es más fácil para el compilador. Esto es importante, los enfoques heredados tienen pocas oportunidades de mejorarse a medida que el compilador es revoked. Lambdas (y otros) que confían en el compilador para expandirlos pueden beneficiarse ya que el compilador los trata mejor con el tiempo.

Para resumir:

  1. Los desarrolladores pueden manejarlo
  2. Todo el mundo lo está haciendo
  3. Hay potencial futuro

Una vez más, sé que esto no será una respuesta popular. Y créanme "Simple is Best" es mi mantra, también. El mantenimiento es un aspecto importante para cualquier fuente. Lo entiendo. Pero creo que estamos eclipsando la realidad con algunas reglas generales cliché.

// Jerry

+9

Lo único malo con esta respuesta es el error en la primera oración :) –

+3

No puedo creer que no mencione que la reducción de las líneas de código aumenta la legibilidad de su fuente. Puedo absorber 2.700 líneas mucho más fácilmente que 27,000. Lambdas ayuda a reducir las líneas de código. –

+4

Esta respuesta tiene dos problemas, uno que no está respondiendo la pregunta, la pregunta es sobre ese 1%. Dos, comienzas de inmediato con "mejor", pero ¿mejor que qué? Si te refieres a la palabra clave 'delegate', estoy de acuerdo. Si te refieres a algo mejor que escribir métodos con nombre, entonces no estoy de acuerdo – nawfal

2

Lambda expressions are cool. Más viejo delegate sintaxis tienen algunas ventajas como, se pueden convertir a funciones anónimas o árboles de expresión, los tipos de parámetros se deducen de la declaración, son más limpios y más concisos, etc. No veo ningún valor real para no usar expresión lambda cuando necesitas una función anónima. Una ventaja no tan grande que tiene el estilo anterior es que puede omitir la declaración de parámetro totalmente si no se utilizan. Al igual que

Action<int> a = delegate { }; //takes one argument, but no argument specified 

Esto es útil cuando se tiene que declarar un delegado vacío que no hace nada, pero no es una fuerte razónsuficiente como para no utilizar lambdas.

Lambdas le permiten escribir métodos anónimos rápidos. Ahora eso hace que las lambdas no tengan sentido en todos lados donde los métodos anónimos carecen de sentido, es decir, donde los métodos nombrados tienen más sentido. métodos Con el nombre, métodos anónimos pueden ser desventajoso (no es una expresión lambda cosa en sí, pero ya que estos días lambdas ampliamente representar métodos anónimos es relevante):

  1. , ya que tienden a conducir a la duplicación lógica (con qué frecuencia, la reutilización es difícil)

  2. cuando no es necesario para escribir a uno, como:

    //this is unnecessary 
    Func<string, int> f = x => int.Parse(x); 
    
    //this is enough 
    Func<string, int> f = int.Parse; 
    
  3. ya que la escritura de bloque iterador anónimo es i posible.

    Func<IEnumerable<int>> f =() => { yield return 0; }; //impossible 
    
  4. desde lambdas recursivas requieren una línea más de rareza, como

    Func<int, int> f = null; 
    f = x => (x <= 1) ? 1 : x * f(x - 1); 
    
  5. así, ya que la reflexión es un poco más sucio, pero eso es discutible ¿verdad?

Aparte de punto 3, el resto no son fuertes razones para no usar lambda.

También vea esto thread sobre lo que es desfavorable sobre los delegados Func/Action, ya que a menudo se usan junto con las expresiones lambda.