List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
Para mim, a diferença é puramente cosmética, mas existem razões sutis pelas quais uma pode ser preferida à outra?
List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
Para mim, a diferença é puramente cosmética, mas existem razões sutis pelas quais uma pode ser preferida à outra?
Respostas:
Observando o código compilado pelo ILSpy, na verdade há uma diferença nas duas referências. Para um 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));
}
}
}
O ILSpy o 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);
}
);
}
}
}
Se você olhar para a pilha de chamadas IL para ambos, a implementação explícita terá muito mais chamadas (e criará um método gerado):
.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'
enquanto a implementação implícita é mais 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
Eu preferiria a sintaxe lambda em geral . Quando você vê isso, ele diz qual é o tipo. Quando você Console.WriteLine
vir, terá que perguntar ao IDE que tipo é. É claro que, neste exemplo trivial, é óbvio, mas no caso geral, pode não ser tanto.
com os dois exemplos que você deu, eles diferem nisso quando você diz
List.ForEach(Console.WriteLine)
você está realmente dizendo ao loop ForEach para usar o método WriteLine
List.ForEach(s => Console.WriteLine(s));
está realmente definindo um método que o foreach chamará e então você está dizendo a ele o que manipular lá.
Portanto, para liners simples, se o seu método que você chamar chamar tiver a mesma assinatura do método que já é chamado, eu preferiria não definir o lambda, acho que é um pouco mais legível.
para métodos com lambdas incompatíveis são definitivamente um bom caminho a percorrer, supondo que não sejam muito complicados.
Há uma razão muito forte para preferir a primeira linha.
Todo delegado possui uma Target
propriedade que permite que os representantes se refiram aos métodos da instância, mesmo depois que a instância sai do escopo.
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;
Não podemos ligar a1.WriteData();
porque a1
é nulo. No entanto, podemos chamar o action
delegado sem problemas e ele será impresso 4
, porqueaction
mantém uma referência à instância com a qual o método deve ser chamado.
Quando métodos anônimos são passados como delegado em um contexto de instância, o delegado ainda mantém uma referência à classe que contém, mesmo que não seja óbvio:
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));
}
}
Nesse caso específico, é razoável supor que .ForEach
não esteja armazenando o delegado internamente, o que significaria que a instância Container
e todos os seus dados ainda estão sendo mantidos. Mas não há garantia disso; o método que recebe o delegado pode manter o delegado e a instância indefinidamente.
Os métodos estáticos, por outro lado, não têm instância para referência. O seguinte não terá uma referência implícita à instância 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);
}
}