¿Hay alguna razón para preferir la sintaxis lambda incluso si solo hay un parámetro?

14
List.ForEach(Console.WriteLine);

List.ForEach(s => Console.WriteLine(s));

Para mí, la diferencia es puramente cosmética, pero ¿hay alguna razón sutil por la cual uno podría preferir al otro?

    
pregunta Benjol 22.12.2011 - 13:05

4 respuestas

22

Mirando el código compilado a través de ILSpy, en realidad hay una diferencia en las dos referencias. Para un programa simplista como este:

namespace ScratchLambda
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    internal class Program
    {
        private static void Main(string[] args)
        {
            var list = Enumerable.Range(1, 10).ToList();
            ExplicitLambda(list);
            ImplicitLambda(list);
        }

        private static void ImplicitLambda(List<int> list)
        {
            list.ForEach(Console.WriteLine);
        }

        private static void ExplicitLambda(List<int> list)
        {
            list.ForEach(s => Console.WriteLine(s));
        }
    }
}

ILSpy lo descompila como:

using System;
using System.Collections.Generic;
using System.Linq;
namespace ScratchLambda
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            List<int> list = Enumerable.Range(1, 10).ToList<int>();
            Program.ExplicitLambda(list);
            Program.ImplicitLambda(list);
        }
        private static void ImplicitLambda(List<int> list)
        {
            list.ForEach(new Action<int>(Console.WriteLine));
        }
        private static void ExplicitLambda(List<int> list)
        {
            list.ForEach(delegate(int s)
            {
                Console.WriteLine(s);
            }
            );
        }
    }
}

Si observa la pila de llamadas IL para ambas, la implementación Explicit tiene muchas más llamadas (y crea un método generado):

.method private hidebysig static 
    void ExplicitLambda (
        class [mscorlib]System.Collections.Generic.List'1<int32> list
    ) cil managed 
{
    // Method begins at RVA 0x2093
    // Code size 36 (0x24)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldsfld class [mscorlib]System.Action'1<int32> ScratchLambda.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
    IL_0006: brtrue.s IL_0019

    IL_0008: ldnull
    IL_0009: ldftn void ScratchLambda.Program::'<ExplicitLambda>b__0'(int32)
    IL_000f: newobj instance void class [mscorlib]System.Action'1<int32>::.ctor(object, native int)
    IL_0014: stsfld class [mscorlib]System.Action'1<int32> ScratchLambda.Program::'CS$<>9__CachedAnonymousMethodDelegate1'

    IL_0019: ldsfld class [mscorlib]System.Action'1<int32> ScratchLambda.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
    IL_001e: callvirt instance void class [mscorlib]System.Collections.Generic.List'1<int32>::ForEach(class [mscorlib]System.Action'1<!0>)
    IL_0023: ret
} // end of method Program::ExplicitLambda


.method private hidebysig static 
    void '<ExplicitLambda>b__0' (
        int32 s
    ) cil managed 
{
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    // Method begins at RVA 0x208b
    // Code size 7 (0x7)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: call void [mscorlib]System.Console::WriteLine(int32)
    IL_0006: ret
} // end of method Program::'<ExplicitLambda>b__0'

mientras que la implementación implícita es más concisa:

.method private hidebysig static 
    void ImplicitLambda (
        class [mscorlib]System.Collections.Generic.List'1<int32> list
    ) cil managed 
{
    // Method begins at RVA 0x2077
    // Code size 19 (0x13)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldnull
    IL_0002: ldftn void [mscorlib]System.Console::WriteLine(int32)
    IL_0008: newobj instance void class [mscorlib]System.Action'1<int32>::.ctor(object, native int)
    IL_000d: callvirt instance void class [mscorlib]System.Collections.Generic.List'1<int32>::ForEach(class [mscorlib]System.Action'1<!0>)
    IL_0012: ret
} // end of method Program::ImplicitLambda
    
respondido por el Agent_9191 22.12.2011 - 15:24
2

Prefiero la sintaxis lambda en general . Cuando ves eso, entonces te dice cuál es el tipo. Cuando vea Console.WriteLine , tendrá que preguntarle al IDE qué tipo es. Por supuesto, en este ejemplo trivial, es obvio, pero en el caso general, puede que no sea tanto.

    
respondido por el DeadMG 22.12.2011 - 13:09
1

con los dos ejemplos que dio, difieren en eso cuando dice

List.ForEach(Console.WriteLine) 

en realidad le está diciendo a ForEach Loop que use el método WriteLine

List.ForEach(s => Console.WriteLine(s));

en realidad está definiendo un método que foreach llamará y luego eres diciéndole qué manejar allí.

así que para líneas simples simples si su método al que va a llamar tiene la misma firma que el método al que ya se llama, preferiría no definir la lambda, creo que es un poco más legible.

para métodos con lambdas incompatibles es definitivamente una buena manera de hacerlo, asumiendo que no son demasiado complicados.

    
respondido por el tam 22.12.2011 - 15:15
1

Hay una razón muy fuerte para preferir la primera línea.

Cada delegado tiene una propiedad Target , que permite a los delegados consultar métodos de instancia, incluso después de que la instancia haya quedado fuera del alcance.

public class A {
    public int Data;
    public void WriteData() {
        Console.WriteLine(this.Data);
    }
}

var a1 = new A() {Data=4};
Action action = a1.WriteData;
a1 = null;

No podemos llamar a a1.WriteData(); porque a1 es nulo. Sin embargo, podemos invocar el delegado action sin problemas, y se imprimirá 4 , porque action contiene una referencia a la instancia con la que se debe llamar al método.

Cuando los métodos anónimos se pasan como un delegado en un contexto de instancia, el delegado seguirá manteniendo una referencia a la clase contenedora, aunque no sea obvio:

public class Container {
    private List<int> data = new List<int>() {1,2,3,4,5};
    public void PrintItems() {
        //There is an implicit reference to an instance of Container here
        data.ForEach(s => Console.WriteLine(s));
    }
}

En este caso específico, es razonable suponer que .ForEach no está almacenando el delegado internamente, lo que significaría que la instancia de Container y todos sus datos aún se conservan. Pero no hay garantía de eso; el método que recibe el delegado puede ser válido para el delegado y la instancia por tiempo indefinido.

Los métodos estáticos, por otro lado, no tienen ninguna instancia a la que hacer referencia. Lo siguiente no tendrá una referencia implícita a la instancia de Container :

public class Container {
    private List<int> data = new List<int>() {1,2,3,4,5};
    public void PrintItems() {
        //Since Console.WriteLine is a static method, there is no implicit reference
        data.ForEach(Console.WriteLine);
    }
}
    
respondido por el Zev Spitz 14.11.2016 - 13:28

Lea otras preguntas en las etiquetas