List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
สำหรับฉันแล้วความแตกต่างนั้นเป็นเครื่องสำอางล้วนๆ แต่มีเหตุผลใดที่ลึกซึ้งว่าทำไมคน ๆ หนึ่งถึงชอบกัน
List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
สำหรับฉันแล้วความแตกต่างนั้นเป็นเครื่องสำอางล้วนๆ แต่มีเหตุผลใดที่ลึกซึ้งว่าทำไมคน ๆ หนึ่งถึงชอบกัน
คำตอบ:
การดูรหัสที่คอมไพล์ผ่าน 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
ฉันไม่ต้องการไวยากรณ์แลมบ์ดาทั่วไป เมื่อคุณเห็นสิ่งนั้นมันจะบอกคุณว่าประเภทนั้นคืออะไร เมื่อคุณเห็นConsole.WriteLine
คุณจะต้องถาม IDE ว่าเป็นประเภทใด แน่นอนในตัวอย่างเล็กน้อยนี้มันชัดเจน แต่ในกรณีทั่วไปอาจไม่มากนัก
ด้วยสองตัวอย่างที่คุณให้มาพวกเขาต่างกันเมื่อคุณพูด
List.ForEach(Console.WriteLine)
คุณกำลังบอก ForEach Loop ให้ใช้เมธอด WriteLine
List.ForEach(s => Console.WriteLine(s));
จริง ๆ แล้วเป็นการกำหนดวิธีการที่ foreach จะเรียกใช้จากนั้นคุณกำลังบอกว่าจะจัดการกับสิ่งนั้นอย่างไร
ดังนั้นสำหรับหนึ่ง liners ง่ายถ้าวิธีการของคุณคุณจะโทรมีลายเซ็นเช่นเดียวกับวิธีการที่ได้รับการเรียกแล้วฉันไม่ต้องการกำหนดแลมบ์ดาฉันคิดว่ามันอ่านง่ายขึ้นเล็กน้อย
สำหรับวิธีที่มี lambdas ที่เข้ากันไม่ได้นั้นเป็นวิธีที่ดีที่จะไปหากว่าพวกมันไม่ซับซ้อนเกินไป
มีเหตุผลที่ดีมากในการเลือกบรรทัดแรก
ผู้รับมอบสิทธิ์ทุกคนมี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);
}
}