List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
Dla mnie różnica jest czysto kosmetyczna, ale czy istnieją jakieś subtelne powody, dla których jedno może być preferowane nad drugim?
List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
Dla mnie różnica jest czysto kosmetyczna, ale czy istnieją jakieś subtelne powody, dla których jedno może być preferowane nad drugim?
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:
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 dekompiluje to jako:
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);
}
);
}
}
}
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 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'
podczas gdy implementacja niejawna jest bardziej zwięzła:
.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
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.
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.
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.
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;
Nie możemy zadzwonić, a1.WriteData();
ponieważ a1
ma wartość zero. Możemy jednak action
bez problemu wywołać delegata, który wydrukuje 4
, 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:
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));
}
}
W tym konkretnym przypadku uzasadnione jest założenie, że .ForEach
nie przechowuje się delegata wewnętrznie, co oznaczałoby, że instancja Container
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
:
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);
}
}