Yalnızca bir parametre olsa bile lambda sözdizimini tercih etmek için bir neden var mı?


14
List.ForEach(Console.WriteLine);

List.ForEach(s => Console.WriteLine(s));

Bana göre fark tamamen kozmetiktir, ancak birinin diğerine tercih edilmesinin ince nedenleri var mı?


Deneyimlerime göre, ikinci sürüm tercih edilebilir göründüğünde, genellikle söz konusu yöntemin kötü adlandırılmasından kaynaklanıyordu.
Roman Reiner

Yanıtlar:


23

Derlenmiş koda ILSpy aracılığıyla bakıldığında, aslında iki referansta bir fark vardır. Bunun gibi basit bir program için:

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 şu şekilde ayrıştırır:

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);
            }
            );
        }
    }
}

Her ikisi için de IL çağrı yığınına bakarsanız, Explicit uygulaması çok daha fazla çağrıya sahiptir (ve oluşturulan bir yöntem oluşturur):

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

Örtük uygulama daha özlü olsa da:

.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

Bunun hızlı bir çizik programından kodun sürümünün oluşturulduğunu unutmayın, bu nedenle daha fazla optimizasyon için yer olabilir. Ancak bu, Visual Studio'nun varsayılan çıktısıdır.
Agent_9191

2
+1 Bunun nedeni lambda sözdiziminin aslında ham yöntem çağrısını anonim bir işleve <i> sebepsiz </i> sarmasıdır. Bu tamamen anlamsızdır, dolayısıyla ham yöntem grubunu kullanılabilir olduğunda Func <> parametresi olarak kullanmalısınız.
Ed James

Vay be, araştırma için yeşil onay işareti aldınız!
Benjol

2

Genel olarak lambda sözdizimini tercih ederim . Bunu gördüğünüzde, türün ne olduğunu size söyler. Gördüğünüzde Console.WriteLine, IDE'ye ne tür olduğunu sormanız gerekir. Tabii ki, bu önemsiz örnekte açıktır, ancak genel durumda, çok fazla olmayabilir.


Gerekli olduğu durumlarda tutarlılık için labmda sözdizimini tercih ederim.
bunglestink

4
Ben bir C # kişi değilim, ama lambdas (JavaScript, Şema ve Haskell) ile kullandığım dillerde insanlar muhtemelen size tam tersi tavsiye. Bence bu sadece stilin dile bağlı olduğunu gösterir.
Tikhon Jelvis

size hangi yolla yazıyor? Kesinlikle bir lambdas parametresi türü hakkında açık olabilir ama bunu yapmak için ortak olmaktan çok uzak ve bu durumda yapılmaz
jk.

1

verdiğiniz iki örnekle,

List.ForEach(Console.WriteLine) 

aslında ForEach Loop'a WriteLine yöntemini kullanmasını söylüyorsunuz

List.ForEach(s => Console.WriteLine(s));

aslında foreach'in arayacağı bir yöntem tanımlamakta ve o zaman orada ne işleneceğini söylüyorsunuz.

yani basit bir gömlekleri için eğer yöntemi arayacaksanız zaten imzalanan yöntemle aynı imzayı taşıyorsa ben lambda tanımlamak için tercih etmem, biraz daha okunabilir düşünüyorum.

çünkü uyumsuz lambdaları olan yöntemler, aşırı derecede karmaşık olmadıklarını varsayarak, kesinlikle iyi bir yoldur.


1

İlk çizgiyi tercih etmek için çok güçlü bir neden var.

Her delege, Targetdelegelerin örnek kapsam dışına çıktıktan sonra bile örnek yöntemlerine başvurmalarını sağlayan bir özelliğe sahiptir.

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;

Arayamayız a1.WriteData();çünkü a1null. Ancak, actiontemsilcisini sorunsuz bir şekilde çağırabiliriz ve yazdırılır 4, çünkü actionyöntemin çağrılması gereken örneğe başvuruda bulunur.

Anonim yöntemler bir örnek bağlamında temsilci olarak iletildiğinde, temsilci, açık olmasa da, yine de içeren sınıfa başvuruda bulunur:

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));
    }
}

Bu özel durumda, .ForEachdelegeyi dahili olarak saklamadığını varsaymak mantıklıdır; bu, örneğinin Containerve tüm verilerinin hala saklandığı anlamına gelir. Ama bunun garantisi yok; temsilci alan yöntem temsilci ve örnek üzerinde süresiz olarak bekletilebilir.

Statik yöntemlerin ise referans gösterecek bir örneği yoktur. Aşağıdakilerin örneği için örtük bir başvurusu olmayacaktır 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);
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.