Existe um motivo para preferir a sintaxe lambda mesmo se houver apenas um parâmetro?

14
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?

Benjol
fonte
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;

    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
Agent_9191
fonte
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.

DeadMG
fonte
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.

tam
fonte
1

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.

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 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:

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 .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:

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);
    }
}
Zev Spitz
fonte