มีเหตุผลที่จะชอบไวยากรณ์แลมบ์ดาแม้ว่าจะมีเพียงพารามิเตอร์เดียวหรือไม่?


14
List.ForEach(Console.WriteLine);

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

สำหรับฉันแล้วความแตกต่างนั้นเป็นเครื่องสำอางล้วนๆ แต่มีเหตุผลใดที่ลึกซึ้งว่าทำไมคน ๆ หนึ่งถึงชอบกัน


จากประสบการณ์ของฉันเมื่อใดก็ตามที่รุ่นที่สองดูน่าจะเป็นเพราะการตั้งชื่อไม่ดีของวิธีการที่เป็นปัญหา
Roman Reiner

คำตอบ:


23

การดูรหัสที่คอมไพล์ผ่าน ILSpy มีความแตกต่างในการอ้างอิงทั้งสอง สำหรับโปรแกรมแบบง่าย ๆ เช่นนี้:

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 ถอดรหัสมันเป็น:

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

หากคุณมองไปที่ IL call stack สำหรับทั้งสองการใช้งานอย่างชัดเจนนั้นมีการเรียกมากขึ้น (และสร้างวิธีการที่สร้างขึ้น):

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

ในขณะที่การใช้งานโดยนัยนั้นรัดกุมกว่า:

.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

โปรดทราบว่านี่เป็นโครงสร้างการวางจำหน่ายของรหัสจากโปรแกรม scratch ด่วนดังนั้นอาจมีพื้นที่สำหรับการปรับให้เหมาะสมเพิ่มเติม แต่นี่เป็นเอาต์พุตเริ่มต้นจาก Visual Studio
Agent_9191

2
+1 นั่นเป็นเพราะไวยากรณ์แลมบ์ดากำลังห่อการเรียกใช้เมธอด raw ในฟังก์ชันที่ไม่ระบุชื่อ <i> โดยไม่มีเหตุผล </i> สิ่งนี้ไม่มีจุดหมายอย่างสมบูรณ์ดังนั้นคุณควรใช้กลุ่มเมธอด raw เป็นพารามิเตอร์ Func <> เมื่อพร้อมใช้งาน
Ed James

ว้าวคุณจะได้รับเครื่องหมายสีเขียวสำหรับการวิจัย!
Benjol

2

ฉันไม่ต้องการไวยากรณ์แลมบ์ดาทั่วไป เมื่อคุณเห็นสิ่งนั้นมันจะบอกคุณว่าประเภทนั้นคืออะไร เมื่อคุณเห็นConsole.WriteLineคุณจะต้องถาม IDE ว่าเป็นประเภทใด แน่นอนในตัวอย่างเล็กน้อยนี้มันชัดเจน แต่ในกรณีทั่วไปอาจไม่มากนัก


ฉันต้องการไวยากรณ์ labmda เพื่อความสอดคล้องกับกรณีที่จำเป็น
bunglestink

4
ฉันไม่ได้อยู่ในคน C # แต่ในภาษาที่ฉันใช้กับ lambdas (JavaScript, Scheme และ Haskell) ผู้คนอาจจะให้คำแนะนำที่ตรงข้ามกับคุณ ฉันคิดว่าเพียงแค่แสดงให้เห็นว่ารูปแบบที่ดีขึ้นอยู่กับภาษา
Tikhon Jelvis

มันบอกคุณในรูปแบบใด? แน่นอนคุณสามารถชัดเจนเกี่ยวกับประเภทของพารามิเตอร์ lambdas แต่มันไกลจากสามัญที่จะทำเช่นนั้นและไม่ได้ทำในสถานการณ์นี้
jk

1

ด้วยสองตัวอย่างที่คุณให้มาพวกเขาต่างกันเมื่อคุณพูด

List.ForEach(Console.WriteLine) 

คุณกำลังบอก ForEach Loop ให้ใช้เมธอด WriteLine

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

จริง ๆ แล้วเป็นการกำหนดวิธีการที่ foreach จะเรียกใช้จากนั้นคุณกำลังบอกว่าจะจัดการกับสิ่งนั้นอย่างไร

ดังนั้นสำหรับหนึ่ง liners ง่ายถ้าวิธีการของคุณคุณจะโทรมีลายเซ็นเช่นเดียวกับวิธีการที่ได้รับการเรียกแล้วฉันไม่ต้องการกำหนดแลมบ์ดาฉันคิดว่ามันอ่านง่ายขึ้นเล็กน้อย

สำหรับวิธีที่มี lambdas ที่เข้ากันไม่ได้นั้นเป็นวิธีที่ดีที่จะไปหากว่าพวกมันไม่ซับซ้อนเกินไป


1

มีเหตุผลที่ดีมากในการเลือกบรรทัดแรก

ผู้รับมอบสิทธิ์ทุกคนมีTargetคุณสมบัติซึ่งอนุญาตให้ผู้ร่วมประชุมอ้างอิงถึงวิธีการอินสแตนซ์แม้หลังจากที่อินสแตนซ์หมดขอบเขต

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;

เราไม่สามารถโทรa1.WriteData();เพราะa1เป็นโมฆะ อย่างไรก็ตามเราสามารถเรียกactionผู้ร่วมประชุมได้โดยไม่มีปัญหาและมันจะพิมพ์4เนื่องจากactionมีการอ้างอิงถึงอินสแตนซ์ที่ควรเรียกใช้เมธอด

เมื่อวิธีการที่ไม่ระบุชื่อถูกส่งผ่านเป็นผู้รับมอบสิทธิ์ในบริบทอินสแตนซ์ผู้รับมอบสิทธิ์จะยังคงมีการอ้างอิงถึงคลาสที่ประกอบด้วยแม้ว่าจะไม่ชัดเจน:

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

ในกรณีเฉพาะนี้มีเหตุผลที่จะถือว่า.ForEachไม่ได้จัดเก็บผู้รับมอบสิทธิ์ภายในซึ่งหมายความว่าอินสแตนซ์ของContainerและข้อมูลทั้งหมดยังคงอยู่ แต่ไม่มีผู้รับรองว่า วิธีการที่ได้รับมอบหมายอาจค้างไว้ที่ผู้รับมอบสิทธิ์และอินสแตนซ์ไม่สิ้นสุด

ในทางกลับกันวิธีคงที่ไม่มีตัวอย่างอ้างอิง ต่อไปนี้จะไม่มีการอ้างอิงโดยนัยถึงอินสแตนซ์ของ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);
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.