List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
Para mí, la diferencia es puramente cosmética, pero ¿hay alguna razón sutil por la cual uno podría preferirse sobre el otro?
List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
Para mí, la diferencia es puramente cosmética, pero ¿hay alguna razón sutil por la cual uno podría preferirse sobre el otro?
Respuestas:
Mirando el código compilado a través de ILSpy, en realidad hay una diferencia en las dos referencias. Para un programa simplista como este:
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 descompila como:
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);
}
);
}
}
}
Si observa la pila de llamadas de IL para ambos, la implementación explícita tiene muchas más llamadas (y crea un método generado):
.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'
mientras que la implementación implícita es más 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
Prefiero la sintaxis lambda en general . Cuando veas eso, te dirá cuál es el tipo. Cuando vea Console.WriteLine
, tendría que preguntarle al IDE de qué tipo es. Por supuesto, en este ejemplo trivial, es obvio, pero en el caso general, podría no ser tanto.
con los dos ejemplos que diste, difieren en eso cuando dices
List.ForEach(Console.WriteLine)
en realidad le estás diciendo al ForEach Loop que use el método WriteLine
List.ForEach(s => Console.WriteLine(s));
en realidad está definiendo un método que llamará el foreach y luego le está diciendo qué manejar allí.
por lo tanto, para líneas simples, si su método al que va a llamar lleva la misma firma que el método que ya se llamó, preferiría no definir la lambda, creo que es un poco más legible.
Los métodos con lambdas incompatibles son definitivamente una buena opción, suponiendo que no sean demasiado complicados.
Hay una razón muy fuerte para preferir la primera línea.
Cada delegado tiene una Target
propiedad, que permite a los delegados referirse a métodos de instancia, incluso después de que la instancia se haya salido del alcance.
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;
No podemos llamar a1.WriteData();
porque a1
es nulo. Sin embargo, podemos invocar al action
delegado sin problemas, y se imprimirá 4
, porque action
contiene una referencia a la instancia con la que se debe llamar al método.
Cuando los métodos anónimos se pasan como delegados en un contexto de instancia, el delegado aún tendrá una referencia a la clase que lo contiene, aunque no sea obvio:
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));
}
}
En este caso específico, es razonable suponer que .ForEach
no está almacenando el delegado internamente, lo que significaría que la instancia deContainer
y todos sus datos aún se conservan. Pero no hay garantía de eso; el método que recibe al delegado puede retener al delegado y la instancia indefinidamente.
Los métodos estáticos, por otro lado, no tienen una instancia de referencia. Lo siguiente no tendrá una referencia implícita a la instancia de 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);
}
}