C'è un motivo per preferire la sintassi lambda anche se esiste un solo parametro?

14
List.ForEach(Console.WriteLine);

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

Per me, la differenza è puramente cosmetica, ma ci sono ragioni per cui uno potrebbe essere preferito rispetto all'altro?

    
posta Benjol 22.12.2011 - 13:05
fonte

4 risposte

22

Guardando il codice compilato tramite ILSpy, c'è effettivamente una differenza nei due riferimenti. Per un programma semplicistico come questo:

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 decompila come:

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);
            }
            );
        }
    }
}

Se guardi allo stack di chiamate IL per entrambi, l'implementazione Explicit ha molte più chiamate (e crea un metodo generato):

.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'

mentre l'implementazione implicita è più 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
    
risposta data 22.12.2011 - 15:24
fonte
2

Preferirei la sintassi lambda in generale . Quando lo vedi, allora ti dice di che tipo si tratta. Quando vedi Console.WriteLine , dovresti chiedere all'IDE di che tipo si tratta. Certo, in questo banale esempio, è ovvio, ma nel caso generale, potrebbe non essere così tanto.

    
risposta data 22.12.2011 - 13:09
fonte
1

con i due esempi che hai dato differiscono in questo quando dici

List.ForEach(Console.WriteLine) 

stai effettivamente dicendo a ForEach Loop di usare il metodo WriteLine

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

sta effettivamente definendo un metodo che il foreach chiamerà e quindi lo sei dicendogli cosa gestire lì.

quindi per semplici linee se il tuo metodo che chiamerai porta la stessa firma del metodo che viene chiamato già Preferirei non definire la lambda, penso che sia un po 'più leggibile.

per i metodi con lambda incompatibili sono sicuramente un buon modo per andare, assumendo che non siano troppo complicati.

    
risposta data 22.12.2011 - 15:15
fonte
1

C'è una ragione molto strong per preferire la prima linea.

Ogni delegato ha una proprietà Target , che consente ai delegati di fare riferimento ai metodi di istanza, anche dopo che l'istanza è stata esclusa dall'ambito.

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;

Non possiamo chiamare a1.WriteData(); perché a1 è nullo. Tuttavia, possiamo invocare il action delegate senza problemi e stamperà 4 , perché action contiene un riferimento all'istanza con cui deve essere chiamato il metodo.

Quando i metodi anonimi vengono passati come delegati in un contesto di istanza, il delegato manterrà ancora un riferimento alla classe contenente, anche se non è ovvio:

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));
    }
}

In questo caso specifico, è ragionevole supporre che .ForEach non stia memorizzando internamente il delegato, il che significherebbe che l'istanza di Container e tutti i suoi dati sono ancora conservati. Ma non c'è garanzia di ciò; il metodo che riceve il delegato potrebbe trattenere il delegato e l'istanza indefinitamente.

I metodi statici, d'altra parte, non hanno istanze di riferimento. Quanto segue non avrà un riferimento implicito all'istanza di 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);
    }
}
    
risposta data 14.11.2016 - 13:28
fonte

Leggi altre domande sui tag