List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
Per me, la differenza è puramente cosmetica, ma ci sono ragioni sottili per cui uno potrebbe essere preferito rispetto all'altro?
List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
Per me, la differenza è puramente cosmetica, ma ci sono ragioni sottili per cui uno potrebbe essere preferito rispetto all'altro?
Risposte:
Guardando il codice compilato tramite ILSpy, in realtà c'è una differenza tra i due riferimenti. Per un programma semplicistico come questo:
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 lo decompila come:
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);
}
);
}
}
}
Se osservi lo stack di chiamate IL per entrambi, l'implementazione esplicita ha molte più chiamate (e crea un metodo generato):
.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'
mentre l'implementazione implicita è più concisa:
.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
Preferirei la sintassi lambda in generale . Quando lo vedi, ti dice che tipo è. Quando vedi Console.WriteLine
, dovresti chiedere all'IDE di che tipo è. Naturalmente, in questo banale esempio, è ovvio, ma nel caso generale, potrebbe non essere così tanto.
con i due esempi che hai dato differiscono in quello quando dici
List.ForEach(Console.WriteLine)
stai effettivamente dicendo al ciclo ForEach di usare il metodo WriteLine
List.ForEach(s => Console.WriteLine(s));
sta effettivamente definendo un metodo che il foreach chiamerà e poi gli stai dicendo cosa gestire lì.
quindi per una semplice linea se il tuo metodo che stai per chiamare porta la stessa firma del metodo che viene chiamato già, preferirei non definire il lambda, penso che sia un po 'più leggibile.
poiché i metodi con lambda incompatibili sono sicuramente una buona strada da percorrere, supponendo che non siano eccessivamente complicati.
C'è una ragione molto forte per preferire la prima linea.
Ogni delegato ha una Target
proprietà, che consente ai delegati di fare riferimento ai metodi dell'istanza, anche dopo che l'istanza è uscita dall'ambito.
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;
Non possiamo chiamare a1.WriteData();
perché a1
è nullo. Tuttavia, possiamo invocare il action
delegato senza problemi e verrà stampato 4
, poiché action
contiene un riferimento all'istanza con cui deve essere chiamato il metodo.
Quando i metodi anonimi vengono passati come delegati in un contesto di istanza, il delegato manterrà comunque un riferimento alla classe contenente, anche se non è ovvio:
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 questo caso specifico, è ragionevole supporre che .ForEach
non stia memorizzando il delegato internamente, il che significherebbe che l'istanza di Container
e tutti i suoi dati sono ancora conservati. Ma non vi è alcuna garanzia; il metodo che riceve il delegato potrebbe trattenere indefinitamente il delegato e l'istanza.
I metodi statici, d'altra parte, non hanno istanza di riferimento. Quanto segue non avrà un riferimento implicito all'istanza di 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);
}
}