Apakah ada alasan untuk lebih menyukai sintaks lambda walaupun hanya ada satu parameter?

14
List.ForEach(Console.WriteLine);

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

Bagi saya, perbedaannya murni kosmetik, tetapi adakah alasan halus mengapa yang satu lebih disukai daripada yang lain?

Benjol
sumber
Dalam pengalaman saya setiap kali versi kedua tampaknya lebih disukai biasanya karena penamaan metode yang buruk.
Roman Reiner

Jawaban:

23

Melihat kode yang dikompilasi melalui ILSpy, sebenarnya ada perbedaan dalam dua referensi. Untuk program sederhana seperti ini:

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

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

Jika Anda melihat tumpukan panggilan IL untuk keduanya, implementasi Eksplisit memiliki lebih banyak panggilan (dan membuat metode yang dihasilkan):

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

sementara implementasi Implisit lebih ringkas:

.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
sumber
Perhatikan bahwa ini adalah versi rilis kode dari program gores cepat, jadi mungkin ada ruang untuk pengoptimalan lebih lanjut. Tapi ini adalah output default dari Visual Studio.
Agent_9191
2
+1 Itu karena sintaks lambda sebenarnya membungkus panggilan metode mentah dalam fungsi anonim <i> tanpa alasan </i>. Ini sama sekali tidak ada gunanya, maka Anda harus menggunakan grup metode mentah sebagai parameter <> ketika fungsi itu tersedia.
Ed James
Wow, Anda mendapatkan tanda centang hijau, untuk penelitian!
Benjol
2

Saya lebih suka sintaks lambda secara umum . Ketika Anda melihat itu, maka itu memberi tahu Anda apa jenisnya. Ketika Anda melihat Console.WriteLine, Anda harus bertanya pada IDE tipe apa itu. Tentu saja, dalam contoh sepele ini, sudah jelas, tetapi dalam kasus umum, mungkin tidak terlalu banyak.

DeadMG
sumber
Saya lebih suka sintaks labmda untuk konsistensi dengan kasus-kasus di mana diperlukan.
bunglestink
4
Saya bukan orang C #, tetapi dalam bahasa yang saya gunakan dengan lambdas (JavaScript, Skema dan Haskell) orang mungkin akan memberi Anda saran yang berlawanan. Saya pikir itu hanya menunjukkan seberapa baik gaya tergantung pada bahasa.
Tikhon Jelvis
dengan cara apa ia memberi tahu Anda tipenya? tentu saja Anda dapat secara eksplisit tentang jenis parameter lambdas tetapi jauh dari umum untuk melakukan itu, dan tidak dilakukan dalam situasi ini
jk.
1

dengan dua contoh yang Anda berikan, mereka berbeda ketika Anda mengatakannya

List.ForEach(Console.WriteLine) 

Anda sebenarnya memberi tahu ForEach Loop untuk menggunakan metode WriteLine

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

sebenarnya mendefinisikan metode yang akan memanggil foreach dan kemudian Anda mengatakan apa yang harus ditangani di sana.

jadi untuk satu baris sederhana jika metode Anda, Anda akan memanggil membawa tanda tangan yang sama dengan metode yang dipanggil sudah saya lebih suka tidak mendefinisikan lambda, saya pikir itu sedikit lebih mudah dibaca.

karena metode dengan lambda yang tidak kompatibel jelas merupakan cara yang baik untuk dilakukan, dengan asumsi mereka tidak terlalu rumit.

tam
sumber
1

Ada alasan yang sangat kuat untuk memilih baris pertama.

Setiap delegasi memiliki Targetproperti, yang memungkinkan delegasi untuk merujuk pada metode instance, bahkan setelah instance sudah keluar dari ruang lingkup.

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;

Kami tidak dapat menelepon a1.WriteData();karena a1tidak ada. Namun, kita dapat memanggil actiondelegasi tanpa masalah, dan itu akan dicetak 4, karena actionmenyimpan referensi ke instance yang harus dipanggil metode.

Ketika metode anonim dilewatkan sebagai delegasi dalam konteks instance, delegasi masih akan memiliki referensi ke kelas yang berisi, meskipun itu tidak jelas:

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

Dalam kasus khusus ini, masuk akal untuk berasumsi bahwa .ForEachtidak menyimpan delegasi secara internal, yang berarti bahwa instance Containerdan semua datanya masih disimpan. Tetapi tidak ada jaminan untuk itu; metode menerima delegasi mungkin berpegang pada delegasi dan mesin virtual tanpa batas.

Metode statis, di sisi lain, tidak memiliki contoh untuk referensi. Berikut ini tidak akan memiliki referensi implisit ke instance dari 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
sumber