2010-10-01 13 views
19

Cuando se trata de algo así como un List<string> puede escribir lo siguiente:¿Hay algún beneficio al usar un grupo de métodos C# si está disponible?

list.ForEach(x => Console.WriteLine(x)); 

o puede utilizar un grupo método para realizar la misma operación:

list.ForEach(Console.WriteLine); 

Yo prefiero la segunda línea de código, porque me parece más limpio, pero ¿hay algún beneficio para esto?

+3

Bueno, ReSharper recomienda la segunda versión. Entonces debería ser el correcto ... –

+4

"Alguien más inteligente dice que así es" no es realmente una explicación de * WHY *. Puede ser correcto. Puede ser la mejor respuesta. Pero eso no responde la pregunta de "por qué". – WernerCD

Respuesta

23

Bueno, echemos un vistazo y veamos qué pasa.

static void MethodGroup() 
{ 
    new List<string>().ForEach(Console.WriteLine); 
} 

static void LambdaExpression() 
{ 
    new List<string>().ForEach(x => Console.WriteLine(x)); 
} 

Esto se compila en la siguiente IL.

.method private hidebysig static void MethodGroup() cil managed 
{ 
    .maxstack 8 
    L_0000: newobj instance void [mscorlib]System.Collections.Generic.List`1<string>::.ctor() 
    L_0005: ldnull 
    L_0006: ldftn void [mscorlib]System.Console::WriteLine(string) 
    L_000c: newobj instance void [mscorlib]System.Action`1<string>::.ctor(object, native int) 
    L_0011: call instance void [mscorlib]System.Collections.Generic.List`1<string>::ForEach(class [mscorlib]System.Action`1<!0>) 
    L_0016: ret 
} 

.method private hidebysig static void LambdaExpression() cil managed 
{ 
    .maxstack 8 
    L_0000: newobj instance void [mscorlib]System.Collections.Generic.List`1<string>::.ctor() 
    L_0005: ldsfld class [mscorlib]System.Action`1<string> Sandbox.Program::CS$<>9__CachedAnonymousMethodDelegate1 
    L_000a: brtrue.s L_001d 
    L_000c: ldnull 
    L_000d: ldftn void Sandbox.Program::<LambdaExpression>b__0(string) 
    L_0013: newobj instance void [mscorlib]System.Action`1<string>::.ctor(object, native int) 
    L_0018: stsfld class [mscorlib]System.Action`1<string> Sandbox.Program::CS$<>9__CachedAnonymousMethodDelegate1 
    L_001d: ldsfld class [mscorlib]System.Action`1<string> Sandbox.Program::CS$<>9__CachedAnonymousMethodDelegate1 
    L_0022: call instance void [mscorlib]System.Collections.Generic.List`1<string>::ForEach(class [mscorlib]System.Action`1<!0>) 
    L_0027: ret 
} 

Aviso cómo el enfoque de grupo método crea un delegado Action<T> para un solo uso y el enfoque de expresión lambda crea un campo delegado anónimo oculta y hace una inicialización de línea si es necesario. Observe la instrucción brtrue al IL_000a.

+0

cómo ¿Puedo ver el IL compilado? –

+0

@ M.H .: Use ILDASM o Reflector. –

+0

@ M.H puede usar LINQPad para ver el IL compilado – mbx

3

Sí; el primero en realidad puede causar una llamada interina extra innecesaria; pasando x a un método que simplemente llama al Console.WriteLine(x); No necesita hacer el primero porque Console.WriteLine ya es un método que coincide con la firma que busca ForEach.

0

Personalmente, también prefiero el segundo porque es menos confuso de depurar, pero en este caso creo que es solo una cuestión de estilo, ya que ambos terminan haciendo lo mismo.

0

No hay beneficios tangibles que no sean más agradables para las personas que les gustan los grupos de métodos y molestar a las personas que no les gustan [por favor.] Además, hace que su código sea incompatible con compiladores anteriores.

-Oisin

+0

¿Qué compiladores aceptan el segundo y no el primero? –

7

Creo que hay un beneficio. En el primer caso, está creando un método anónimo que llama a la función Console.Writeline(string), mientras que en el otro caso solo está pasando la referencia a la función existente.

+3

Sí, esa era mi sensación, también. Me imagino que es posible que el optimizador lo reconozca y elimine la llamada innecesaria, pero cuando es realmente más fácil escribirlo de la "mejor manera", tiene sentido hacerlo, OMI. Escribí una publicación de blog sobre este tema (innecesariamente usando expresiones de Lambda como esta: http://www.andrewbarber.com/post/When-to-Avoid-Lambda-Expressions-or-What-Anonymous-Methods.aspx) –

7

Hay un nivel adicional de indirección al usar la expresión lambda. Con una expresión de no cierre como esa, simplemente tendrá una llamada de método adicional en el medio, como lo mencionaron otros.

Sin embargo, hay algunas diferencias interesantes. En el segundo caso, se está creando una nueva instancia de delegado en cada llamada. Para el primero, el delegado se crea una vez y se almacena en caché como un campo oculto, por lo que si llama mucho ahorrará en asignaciones.

Además, si introduce una variable local en la expresión lambda, se convierte en un cierre y en lugar de generar solo un método local, se creará una nueva clase para contener esta información, lo que significa una asignación adicional allí.

8

Como han notado otros, existe una capa innecesaria adicional de indirección inducida por la lambda. Sin embargo, también hay sutiles diferencias lingüísticas. Por ejemplo, en C# 3, la inferencia de tipo genérico funciona de manera diferente en M(F) que en M(x=>F(x)) cuando se intenta realizar una inferencia de tipo de retorno.

Para más detalles ver:

http://blogs.msdn.com/b/ericlippert/archive/2007/11/05/c-3-0-return-type-inference-does-not-work-on-member-groups.aspx

y el seguimiento:

http://blogs.msdn.com/b/ericlippert/archive/2008/05/28/method-type-inference-changes-part-zero.aspx

Cuestiones relacionadas