Z mojego doświadczenia wynika, że zawsze, gdy druga wersja wydawała się lepsza, zwykle było to spowodowane złym nazwaniem omawianej metody.
Roman Reiner
Odpowiedzi:
23
Patrząc na skompilowany kod za pomocą ILSpy, w rzeczywistości istnieją dwie różnice w dwóch odnośnikach. W przypadku takiego uproszczonego programu:
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 dekompiluje to jako:
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);});}}}
Jeśli spojrzysz na stos wywołań IL dla obu, implementacja Jawna zawiera o wiele więcej wywołań (i tworzy wygenerowaną metodę):
.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'
podczas gdy implementacja niejawna jest bardziej zwięzła:
Należy pamiętać, że jest to wersja kompilacji kodu z programu szybkiego scratchowania, więc może być miejsce na dalszą optymalizację. Ale to jest domyślne wyjście z Visual Studio.
Agent_9191,
2
+1 To dlatego, że składnia lambda faktycznie otacza surowe wywołanie metody w anonimowej funkcji <i> bez powodu </i>. Jest to całkowicie bezcelowe, dlatego powinieneś użyć surowej grupy metod jako parametru Func <>, gdy jest on dostępny.
Ed James
Wow, dostałeś zielonego kleszcza, do badań!
Benjol,
2
Wolałbym ogólnie składnię lambda . Kiedy to zobaczysz, powie ci, jaki jest typ. Kiedy zobaczysz Console.WriteLine, będziesz musiał zapytać IDE, jaki to typ. Oczywiście w tym trywialnym przykładzie jest to oczywiste, ale w ogólnym przypadku może nie być tak wiele.
Wolę składnię labmda dla spójności z przypadkami, w których jest to wymagane.
bunglestink
4
Nie jestem osobą w języku C #, ale w językach, których używałem z lambdas (JavaScript, Scheme i Haskell), ludzie prawdopodobnie dawaliby ci przeciwne rady. Myślę, że to pokazuje, jak dobry styl zależy od języka.
Tikhon Jelvis,
w jaki sposób mówi ci typ? z pewnością możesz wyraźnie powiedzieć o typie parametru lambdas, ale nie jest to często spotykane i nie robi się tego w tej sytuacji
jk.
1
w dwóch podanych przez ciebie przykładach różnią się tym, co mówisz
List.ForEach(Console.WriteLine)
tak naprawdę mówisz ForEach Loop, aby używał metody WriteLine
List.ForEach(s =>Console.WriteLine(s));
faktycznie definiuje metodę, którą wywoła foreach, a następnie mówisz jej, co z tym zrobić.
więc dla prostych linijek, jeśli twoja metoda, którą zamierzasz wywołać, nosi ten sam podpis, co metoda, która została już wywołana Wolałbym nie definiować lambda, myślę, że jest to trochę bardziej czytelne.
ponieważ metody z niekompatybilnymi lambdami są zdecydowanie dobrą drogą, zakładając, że nie są zbyt skomplikowane.
Nie możemy zadzwonić, a1.WriteData();ponieważ a1ma wartość zero. Możemy jednak actionbez problemu wywołać delegata, który wydrukuje 4, ponieważ actionzawiera odwołanie do instancji, z którą należy wywołać metodę.
Gdy anonimowe metody są przekazywane jako delegat w kontekście instancji, delegat nadal będzie zawierał odwołanie do zawierającej klasy, nawet jeśli nie jest to oczywiste:
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));}}
W tym konkretnym przypadku uzasadnione jest założenie, że .ForEachnie przechowuje się delegata wewnętrznie, co oznaczałoby, że instancja Containeri wszystkie jej dane są nadal przechowywane. Ale nie ma na to gwarancji; metoda odbierająca delegata może zatrzymać delegata i instancję na czas nieokreślony.
Z drugiej strony metody statyczne nie mają instancji, do których można się odwoływać. Poniższe informacje nie będą miały domyślnego odniesienia do wystąpienia 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);}}
Odpowiedzi:
Patrząc na skompilowany kod za pomocą ILSpy, w rzeczywistości istnieją dwie różnice w dwóch odnośnikach. W przypadku takiego uproszczonego programu:
ILSpy dekompiluje to jako:
Jeśli spojrzysz na stos wywołań IL dla obu, implementacja Jawna zawiera o wiele więcej wywołań (i tworzy wygenerowaną metodę):
podczas gdy implementacja niejawna jest bardziej zwięzła:
źródło
Wolałbym ogólnie składnię lambda . Kiedy to zobaczysz, powie ci, jaki jest typ. Kiedy zobaczysz
Console.WriteLine
, będziesz musiał zapytać IDE, jaki to typ. Oczywiście w tym trywialnym przykładzie jest to oczywiste, ale w ogólnym przypadku może nie być tak wiele.źródło
w dwóch podanych przez ciebie przykładach różnią się tym, co mówisz
tak naprawdę mówisz ForEach Loop, aby używał metody WriteLine
faktycznie definiuje metodę, którą wywoła foreach, a następnie mówisz jej, co z tym zrobić.
więc dla prostych linijek, jeśli twoja metoda, którą zamierzasz wywołać, nosi ten sam podpis, co metoda, która została już wywołana Wolałbym nie definiować lambda, myślę, że jest to trochę bardziej czytelne.
ponieważ metody z niekompatybilnymi lambdami są zdecydowanie dobrą drogą, zakładając, że nie są zbyt skomplikowane.
źródło
Jest bardzo silny powód, aby preferować pierwszą linię.
Każdy delegat ma
Target
właściwość, która pozwala mu odwoływać się do metod instancji, nawet po tym, jak instancja wyszła poza zakres.Nie możemy zadzwonić,
a1.WriteData();
ponieważa1
ma wartość zero. Możemy jednakaction
bez problemu wywołać delegata, który wydrukuje4
, ponieważaction
zawiera odwołanie do instancji, z którą należy wywołać metodę.Gdy anonimowe metody są przekazywane jako delegat w kontekście instancji, delegat nadal będzie zawierał odwołanie do zawierającej klasy, nawet jeśli nie jest to oczywiste:
W tym konkretnym przypadku uzasadnione jest założenie, że
.ForEach
nie przechowuje się delegata wewnętrznie, co oznaczałoby, że instancjaContainer
i wszystkie jej dane są nadal przechowywane. Ale nie ma na to gwarancji; metoda odbierająca delegata może zatrzymać delegata i instancję na czas nieokreślony.Z drugiej strony metody statyczne nie mają instancji, do których można się odwoływać. Poniższe informacje nie będą miały domyślnego odniesienia do wystąpienia
Container
:źródło