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 preferir al 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 preferir al otro?
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 IL para ambas, la implementación Explicit 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 ves eso, entonces te dice cuál es el tipo. Cuando vea Console.WriteLine
, tendrá que preguntarle al IDE qué tipo es. Por supuesto, en este ejemplo trivial, es obvio, pero en el caso general, puede que no sea tanto.
con los dos ejemplos que dio, difieren en eso cuando dice
List.ForEach(Console.WriteLine)
en realidad le está diciendo a ForEach Loop que use el método WriteLine
List.ForEach(s => Console.WriteLine(s));
en realidad está definiendo un método que foreach llamará y luego eres diciéndole qué manejar allí.
así que para líneas simples simples si su método al que va a llamar tiene la misma firma que el método al que ya se llama, preferiría no definir la lambda, creo que es un poco más legible.
para métodos con lambdas incompatibles es definitivamente una buena manera de hacerlo, asumiendo que no son demasiado complicados.
Hay una razón muy fuerte para preferir la primera línea.
Cada delegado tiene una propiedad Target
, que permite a los delegados consultar métodos de instancia, incluso después de que la instancia haya quedado fuera 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 a a1.WriteData();
porque a1
es nulo. Sin embargo, podemos invocar el delegado action
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 un delegado en un contexto de instancia, el delegado seguirá manteniendo una referencia a la clase contenedora, 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 de Container
y todos sus datos aún se conservan. Pero no hay garantía de eso; el método que recibe el delegado puede ser válido para el delegado y la instancia por tiempo indefinido.
Los métodos estáticos, por otro lado, no tienen ninguna instancia a la que hacer 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);
}
}