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 sottili per cui uno potrebbe essere preferito rispetto all'altro?


Nella mia esperienza ogni volta che la seconda versione sembrava preferibile, di solito era a causa della scarsa denominazione del metodo in questione.
Roman Reiner,

Risposte:


23

Guardando il codice compilato tramite ILSpy, in realtà c'è una differenza tra i 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 osservi lo stack di chiamate IL per entrambi, l'implementazione esplicita 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

Si noti che questa è la build di rilascio del codice da un programma scratch veloce, quindi potrebbe esserci spazio per un'ulteriore ottimizzazione. Ma questo è l'output predefinito di Visual Studio.
Agent_9191,

2
+1 Questo perché la sintassi lambda sta effettivamente racchiudendo la chiamata del metodo non elaborato in una funzione anonima <i> senza motivo </i>. Questo è completamente inutile, quindi è necessario utilizzare il gruppo di metodi non elaborati come parametro Func <> quando è disponibile.
Ed James,

Caspita, ottieni il segno di spunta verde, per la ricerca!
Benjol,

2

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


Preferirei la sintassi labmda per coerenza con i casi in cui è richiesta.
bunglestink,

4
Non sono in una persona C #, ma nelle lingue che ho usato con lambdas (JavaScript, Scheme e Haskell) le persone probabilmente ti darebbero i consigli opposti. Penso che ciò dimostri solo quanto il buon stile dipenda dalla lingua.
Tikhon Jelvis,

in che modo ti dice il tipo? certamente puoi essere esplicito sul tipo di un parametro lambdas ma è tutt'altro che comune farlo, e non è fatto in questa situazione
jk.

1

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

List.ForEach(Console.WriteLine) 

stai effettivamente dicendo al ciclo ForEach di usare il metodo WriteLine

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

sta effettivamente definendo un metodo che il foreach chiamerà e poi gli stai dicendo cosa gestire lì.

quindi per una semplice linea se il tuo metodo che stai per chiamare porta la stessa firma del metodo che viene chiamato già, preferirei non definire il lambda, penso che sia un po 'più leggibile.

poiché i metodi con lambda incompatibili sono sicuramente una buona strada da percorrere, supponendo che non siano eccessivamente complicati.


1

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

Ogni delegato ha una Targetproprietà, che consente ai delegati di fare riferimento ai metodi dell'istanza, anche dopo che l'istanza è uscita 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 actiondelegato senza problemi e verrà stampato 4, poiché actioncontiene 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à comunque 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 .ForEachnon stia memorizzando il delegato internamente, il che significherebbe che l'istanza di Containere tutti i suoi dati sono ancora conservati. Ma non vi è alcuna garanzia; il metodo che riceve il delegato potrebbe trattenere indefinitamente il delegato e l'istanza.

I metodi statici, d'altra parte, non hanno istanza 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);
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.