Na minha experiência, sempre que a segunda versão parecia preferível, geralmente era por causa da má nomeação do método em questão.
Roman Reiner
Respostas:
23
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;internalclassProgram{privatestaticvoidMain(string[] args){varlist=Enumerable.Range(1,10).ToList();ExplicitLambda(list);ImplicitLambda(list);}privatestaticvoidImplicitLambda(List<int>list){list.ForEach(Console.WriteLine);}privatestaticvoidExplicitLambda(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{internalclassProgram{privatestaticvoidMain(string[] args){List<int>list=Enumerable.Range(1,10).ToList<int>();Program.ExplicitLambda(list);Program.ImplicitLambda(list);}privatestaticvoidImplicitLambda(List<int>list){list.ForEach(newAction<int>(Console.WriteLine));}privatestaticvoidExplicitLambda(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 staticvoidExplicitLambda(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 voidScratchLambda.Program::'<ExplicitLambda>b__0'(int32)
IL_000f: newobj instance voidclass[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 voidclass[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 staticvoid'<ExplicitLambda>b__0'(int32 s
) cil managed
{.custom instance void[mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()=(01000000)// 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:
Observe que esta é a versão do código a partir de um programa de rascunho rápido, portanto, pode haver espaço para otimização adicional. Mas esta é a saída padrão do Visual Studio.
Agent_9191
2
+1 Isso ocorre porque a sintaxe lambda está, na verdade, envolvendo a chamada do método bruto em uma função anônima <i> sem motivo </i>. Isso é completamente inútil, portanto, você deve usar o grupo de métodos brutos como o parâmetro Func <> quando estiver disponível.
Ed James
Uau, você recebe o visto verde, para a pesquisa!
Benjol
2
Eu preferiria a sintaxe lambda em geral . Quando você vê isso, ele diz qual é o tipo. Quando você Console.WriteLinevir, terá que perguntar ao IDE que tipo é. É claro que, neste exemplo trivial, é óbvio, mas no caso geral, pode não ser tanto.
Eu preferiria a sintaxe do labmda por consistência com os casos em que é necessário.
bunglestink
4
Eu não sou uma pessoa em C #, mas nas línguas que usei com as lambdas (JavaScript, Scheme e Haskell) as pessoas provavelmente dariam o conselho oposto. Eu acho que isso mostra apenas como o bom estilo depende da linguagem.
Tikhon Jelvis
de que maneira isso lhe diz o tipo? certamente você pode ser explícito sobre o tipo de um parâmetro lambdas, mas está longe de ser comum fazer isso, e não é feito nessa situação
jk.
1
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 Targetpropriedade que permite que os representantes se refiram aos métodos da instância, mesmo depois que a instância sai do escopo.
Não podemos ligar a1.WriteData();porque a1é nulo. No entanto, podemos chamar o actiondelegado 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:
publicclassContainer{privateList<int> data =newList<int>(){1,2,3,4,5};publicvoidPrintItems(){//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 .ForEachnão esteja armazenando o delegado internamente, o que significaria que a instância Containere 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:
publicclassContainer{privateList<int> data =newList<int>(){1,2,3,4,5};publicvoidPrintItems(){//Since Console.WriteLine is a static method, there is no implicit reference
data.ForEach(Console.WriteLine);}}
Respostas:
Observando o código compilado pelo ILSpy, na verdade há uma diferença nas duas referências. Para um programa simplista como este:
O ILSpy o descompila como:
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):
enquanto a implementação implícita é mais concisa:
fonte
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.fonte
com os dois exemplos que você deu, eles diferem nisso quando você diz
você está realmente dizendo ao loop ForEach para usar o método WriteLine
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.
fonte
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.Não podemos ligar
a1.WriteData();
porquea1
é nulo. No entanto, podemos chamar oaction
delegado sem problemas e ele será impresso4
, 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:
Nesse caso específico, é razoável supor que
.ForEach
não esteja armazenando o delegado internamente, o que significaria que a instânciaContainer
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
:fonte