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;internalclassProgram{privatestaticvoidMain(string[] args){varlist=Enumerable.Range(1,10).ToList();ExplicitLambda(list);ImplicitLambda(list);}privatestaticvoidImplicitLambda(List<int>list){list.ForEach(Console.WriteLine);}privatestaticvoidExplicitLambda(List<int>list){list.ForEach(s =>Console.WriteLine(s));}}}
ILSpy dekompiliert es als:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ScratchLambda{internalclassProgram{privatestaticvoidMain(string[] args){List<int>list=Enumerable.Range(1,10).ToList<int>();Program.ExplicitLambda(list);Program.ImplicitLambda(list);}privatestaticvoidImplicitLambda(List<int>list){list.ForEach(newAction<int>(Console.WriteLine));}privatestaticvoidExplicitLambda(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 staticvoidExplicitLambda(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 voidScratchLambda.Program::'<ExplicitLambda>b__0'(int32)
IL_000f: newobj instance voidclass[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 voidclass[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 staticvoid'<ExplicitLambda>b__0'(int32 s
) cil managed
{.custom instance void[mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()=(01000000)// 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'
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.
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.
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.
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:
publicclassContainer{privateList<int> data =newList<int>(){1,2,3,4,5};publicvoidPrintItems(){//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:
publicclassContainer{privateList<int> data =newList<int>(){1,2,3,4,5};publicvoidPrintItems(){//Since Console.WriteLine is a static method, there is no implicit reference
data.ForEach(Console.WriteLine);}}
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:
ILSpy dekompiliert es als:
Wenn Sie sich den IL-Aufrufstapel für beide ansehen, hat die Explicit-Implementierung viel mehr Aufrufe (und erstellt eine generierte Methode):
Die implizite Implementierung ist prägnanter:
quelle
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.quelle
mit den zwei Beispielen, die Sie gaben, unterscheiden sie sich darin, wenn Sie sagen
Sie weisen die ForEach-Schleife tatsächlich an, die Methode WriteLine zu verwenden
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.
quelle
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.Wir können nicht anrufen,
a1.WriteData();
weila1
null ist. Wir können denaction
Delegaten jedoch problemlos aufrufen , und er wird gedruckt4
, 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:
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
:quelle