Gibt es einen Grund, die Lambda-Syntax zu bevorzugen, auch wenn es nur einen Parameter gibt?

14
List.ForEach(Console.WriteLine);

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

Für mich ist der Unterschied rein kosmetischer Natur, aber gibt es subtile Gründe, warum einer dem anderen vorgezogen werden könnte?

Benjol
quelle
Meiner Erfahrung nach lag die Bevorzugung der zweiten Version in der Regel an der schlechten Benennung der fraglichen Methode.
Roman Reiner

Antworten:

23

Betrachtet man den kompilierten Code über ILSpy, so gibt es tatsächlich einen Unterschied zwischen den beiden Referenzen. Für ein vereinfachtes Programm wie dieses:

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 dekompiliert es als:

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

Wenn Sie sich den IL-Aufrufstapel für beide ansehen, hat die Explicit-Implementierung viel mehr Aufrufe (und erstellt eine generierte Methode):

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

Die implizite Implementierung ist prägnanter:

.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
Agent_9191
quelle
Beachten Sie, dass dies der Release-Build des Codes aus einem schnellen Scratch-Programm ist, sodass möglicherweise Raum für weitere Optimierungen besteht. Dies ist jedoch die Standardausgabe von Visual Studio.
Agent_9191
2
+1 Das liegt daran, dass die Lambda-Syntax den rohen Methodenaufruf in eine anonyme Funktion <i> ohne Grund </ i> einschließt. Dies ist völlig sinnlos. Daher sollten Sie die Raw-Methodengruppe als Func <> -Parameter verwenden, wenn sie verfügbar ist.
Ed James
Wow, du bekommst die grüne Zecke für die Recherche!
Benjol
2

Ich würde die Lambda-Syntax im Allgemeinen vorziehen . Wenn Sie das sehen, dann sagt es Ihnen, was der Typ ist. Wenn Sie sehen Console.WriteLine, müssen Sie die IDE nach dem Typ fragen. Natürlich ist es in diesem trivialen Beispiel offensichtlich, aber im Allgemeinen ist es vielleicht nicht so viel.

DeadMG
quelle
Ich bevorzuge die labmda-Syntax, um die Übereinstimmung mit den Fällen zu gewährleisten, in denen dies erforderlich ist.
Mistkerl
4
Ich bin keine C # -Person, aber in den Sprachen, die ich mit Lambdas (JavaScript, Scheme und Haskell) verwendet habe, würden Ihnen die Leute wahrscheinlich den gegenteiligen Rat geben. Ich denke, das zeigt nur, wie gut der Stil von der Sprache abhängt.
Tikhon Jelvis
Inwiefern sagt es Ihnen den Typ? Sicherlich können Sie den Typ eines Lambdas-Parameters explizit angeben, aber das ist bei weitem nicht üblich und wird in dieser Situation nicht durchgeführt
jk.
1

mit den zwei Beispielen, die Sie gaben, unterscheiden sie sich darin, wenn Sie sagen

List.ForEach(Console.WriteLine) 

Sie weisen die ForEach-Schleife tatsächlich an, die Methode WriteLine zu verwenden

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

definiert tatsächlich eine Methode, die von foreach aufgerufen wird, und Sie geben an, wie dort verfahren werden soll.

Wenn Ihre aufzurufende Methode für einfache Einzeiler die gleiche Signatur aufweist wie die bereits aufgerufene Methode, würde ich es vorziehen, das Lambda nicht zu definieren. Ich denke, es ist ein wenig lesbarer.

Methoden mit inkompatiblen Lambdas sind definitiv ein guter Weg, vorausgesetzt, sie sind nicht zu kompliziert.

tam
quelle
1

Es gibt einen sehr starken Grund, die erste Zeile zu bevorzugen.

Jeder Delegat verfügt über eine TargetEigenschaft, mit der Delegierte auf Instanzmethoden verweisen können, auch wenn die Instanz den Gültigkeitsbereich überschritten hat.

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;

Wir können nicht anrufen, a1.WriteData();weil a1null ist. Wir können den actionDelegaten jedoch problemlos aufrufen , und er wird gedruckt 4, weilaction einen Verweis auf die Instanz enthält, mit der die Methode aufgerufen werden soll.

Wenn anonyme Methoden als Delegat in einem Instanzkontext übergeben werden, enthält der Delegat weiterhin einen Verweis auf die enthaltende Klasse, auch wenn dies nicht offensichtlich ist:

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 diesem speziellen Fall ist anzunehmen, dass .ForEachder Delegat nicht intern gespeichert wird, was bedeuten würde, dass die Instanz vonContainer und alle ihre Daten noch erhalten bleiben. Dafür gibt es jedoch keine Garantie. Die Methode, die den Delegaten empfängt, behält den Delegaten und die Instanz möglicherweise auf unbestimmte Zeit bei.

Statische Methoden haben dagegen keine Referenzinstanz. Im Folgenden wird nicht implizit auf die Instanz von verwiesen 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);
    }
}
Zev Spitz
quelle