2010-10-01 3 views
19

Wenn mit so etwas wie ein List<string> tun können Sie schreiben folgendes:Gibt es Vorteile bei der Verwendung einer C# -Methodengruppe, falls verfügbar?

list.ForEach(x => Console.WriteLine(x)); 

oder Sie können eine Methode Gruppe verwenden die gleiche Operation zu tun:

list.ForEach(Console.WriteLine); 

Ich ziehe die zweite Zeile des Codes, da es sieht für mich sauberer aus, aber gibt es da irgendwelche Vorteile?

+3

Nun, empfiehlt ReSharper die zweite Version. Also sollte es der richtige sein ... –

+4

"Jemand schlauer sagt das ist richtig" ist nicht wirklich eine Erklärung von * WARUM *. Es mag stimmen. Es könnte die beste Antwort sein. Aber das beantwortet nicht die Frage nach dem "Warum". – WernerCD

Antwort

23

Nun, lassen Sie uns einen Blick darauf werfen, was passiert.

Dies wird in die folgende IL kompiliert.

.method private hidebysig static void MethodGroup() cil managed 
{ 
    .maxstack 8 
    L_0000: newobj instance void [mscorlib]System.Collections.Generic.List`1<string>::.ctor() 
    L_0005: ldnull 
    L_0006: ldftn void [mscorlib]System.Console::WriteLine(string) 
    L_000c: newobj instance void [mscorlib]System.Action`1<string>::.ctor(object, native int) 
    L_0011: call instance void [mscorlib]System.Collections.Generic.List`1<string>::ForEach(class [mscorlib]System.Action`1<!0>) 
    L_0016: ret 
} 

.method private hidebysig static void LambdaExpression() cil managed 
{ 
    .maxstack 8 
    L_0000: newobj instance void [mscorlib]System.Collections.Generic.List`1<string>::.ctor() 
    L_0005: ldsfld class [mscorlib]System.Action`1<string> Sandbox.Program::CS$<>9__CachedAnonymousMethodDelegate1 
    L_000a: brtrue.s L_001d 
    L_000c: ldnull 
    L_000d: ldftn void Sandbox.Program::<LambdaExpression>b__0(string) 
    L_0013: newobj instance void [mscorlib]System.Action`1<string>::.ctor(object, native int) 
    L_0018: stsfld class [mscorlib]System.Action`1<string> Sandbox.Program::CS$<>9__CachedAnonymousMethodDelegate1 
    L_001d: ldsfld class [mscorlib]System.Action`1<string> Sandbox.Program::CS$<>9__CachedAnonymousMethodDelegate1 
    L_0022: call instance void [mscorlib]System.Collections.Generic.List`1<string>::ForEach(class [mscorlib]System.Action`1<!0>) 
    L_0027: ret 
} 

Beachten Sie, wie der Methode Gruppenansatz ein Action<T> Delegierten für die einmaligen Gebrauch und den Lambda-Ausdruck Ansatz schafft schafft einen versteckten anonymen Delegaten Feld und hat eine Inline-Initialisierung es, falls erforderlich. Hinweis brtrue Anweisung bei IL_000a.

+0

wie kann ich die kompilierte IL anzeigen? –

+0

@ M.H .: Verwenden Sie ILDASM oder Reflektor. –

+0

@ M.H Sie können LINQPad verwenden, um die kompilierte IL anzuzeigen – mbx

3

Ja; der erste kann tatsächlich dazu führen, dass ein unnötiger zusätzlicher Zwischenruf auftritt; übergibt x an eine Methode, die einfach Console.WriteLine(x); aufruft Sie müssen die erste nicht tun, da Console.WriteLine bereits eine Methode ist, die der Signatur entspricht, nach der ForEach sucht.

0

Persönlich bevorzuge ich auch die zweite, weil es weniger verwirrend zu debuggen ist, aber in diesem Fall denke ich, es ist nur eine Frage des Stils, da beide am Ende die gleiche Sache erledigt bekommen.

0

Keine greifbaren Vorteile, außer dass es für Leute, die Methodengruppen mögen, angenehmer wird und Leute ärgert, die sie nicht mögen [sollte Ihnen gefallen.] Außerdem macht es Ihren Code mit früheren Compilern inkompatibel.

-Oisin

+0

Welche Compiler akzeptieren die zweite und nicht die erste? –

7

Ich glaube, dass es einen Vorteil gibt. Im ersten Fall erstellen Sie eine anonyme Methode, die die Funktion Console.Writeline(string) aufruft, während Sie im anderen Fall nur den Verweis auf die vorhandene Funktion übergeben.

+3

Ja, das war auch mein Gefühl. Ich stelle mir vor, es ist möglich, dass der Optimierer dies erkennt und den zusätzlichen, unnötigen Anruf entfernt, aber wenn es tatsächlich einfacher ist, den "besseren" Weg zu schreiben, macht es Sinn, dies zu tun, IMO. Ich schrieb einen Blog-Post zu diesem Thema (unnötigerweise mit Lambda-Ausdrücke wie folgt: http://www.andrewbarber.com/post/When-to-Avoid-Lambda-Expressions-or-What-Anonymous-Methods.aspx) –

7

Bei Verwendung des Lambda-Ausdrucks gibt es eine zusätzliche Indirektionsebene. Mit einem nicht geschlossenen Ausdruck wie diesem haben Sie einfach einen zusätzlichen Methodenaufruf dazwischen, wie von anderen erwähnt.

Es gibt jedoch ein paar interessante Unterschiede. Im zweiten Fall wird bei jedem Aufruf eine neue Delegateninstanz erstellt. Für Ersteres wird der Delegat einmal erstellt und als verstecktes Feld zwischengespeichert. Wenn Sie also viel aufrufen, sparen Sie bei den Zuweisungen.

Wenn Sie eine lokale Variable in den Lambda-Ausdruck einfügen, wird sie zu einem Abschluss und anstelle einer lokalen Methode wird eine neue Klasse erstellt, die diese Informationen enthält, dh eine zusätzliche Zuweisung.

8

Wie andere bemerkt haben, gibt es eine zusätzliche unnötige Schicht von Indirektion durch das Lambda induziert. Es gibt jedoch auch subtile Unterschiede in der Sprache. Zum Beispiel funktioniert die generische Inferenz in C# 3 unter M(F) anders als unter M(x=>F(x)), wenn versucht wird, Rückschlüsse vom Typ Rückschluss zu treffen.siehe

Einzelheiten:

http://blogs.msdn.com/b/ericlippert/archive/2007/11/05/c-3-0-return-type-inference-does-not-work-on-member-groups.aspx

und das Follow-up:

http://blogs.msdn.com/b/ericlippert/archive/2008/05/28/method-type-inference-changes-part-zero.aspx

Verwandte Themen