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?
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?
Antworten:
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
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.
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.
Es gibt einen sehr starken Grund, die erste Zeile zu bevorzugen.
Jeder Delegat verfügt über eine Target
Eigenschaft, 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 a1
null ist. Wir können den action
Delegaten 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 .ForEach
der 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);
}
}