Có một lý do để thích cú pháp lambda ngay cả khi chỉ có một tham số?


14
List.ForEach(Console.WriteLine);

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

Đối với tôi, sự khác biệt hoàn toàn là mỹ phẩm, nhưng có lý do tinh tế nào khiến người ta có thể được ưu tiên hơn người kia không?


Theo kinh nghiệm của tôi, bất cứ khi nào phiên bản thứ hai có vẻ thích hợp hơn thì thường là do việc đặt tên phương thức kém.
Roman Reiner

Câu trả lời:


23

Nhìn vào mã được biên dịch thông qua ILSpy, thực sự có một sự khác biệt trong hai tài liệu tham khảo. Đối với một chương trình đơn giản như thế này:

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 dịch ngược nó thành:

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

Nếu bạn nhìn vào ngăn xếp cuộc gọi IL cho cả hai, thì việc triển khai Rõ ràng có nhiều cuộc gọi hơn (và tạo ra một phương thức được tạo):

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

trong khi việc thực hiện ngầm định ngắn gọn hơn:

.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

Lưu ý rằng đây là bản phát hành mã từ chương trình cào nhanh, do đó có thể có chỗ để tối ưu hóa thêm. Nhưng đây là đầu ra mặc định từ Visual Studio.
Đặc vụ_9191

2
+1 Đó là vì cú pháp lambda thực sự bao bọc cuộc gọi phương thức thô trong một hàm ẩn danh <i> mà không có lý do </ i>. Điều này là hoàn toàn vô nghĩa, do đó bạn nên sử dụng nhóm phương thức thô làm tham số Func <> khi có sẵn.
Ed James

Wow, bạn nhận được đánh dấu màu xanh lá cây, cho các nghiên cứu!
Stewol

2

Tôi thích cú pháp lambda nói chung . Khi bạn thấy điều đó, thì nó sẽ cho bạn biết loại đó là gì. Khi bạn thấy Console.WriteLine, bạn phải hỏi IDE đó là loại gì. Tất nhiên, trong ví dụ tầm thường này, điều đó là hiển nhiên, nhưng trong trường hợp chung, nó có thể không quá nhiều.


Tôi thích cú pháp labmda cho sự thống nhất với các trường hợp bắt buộc.
bunglestink

4
Tôi không phải là người C #, nhưng trong các ngôn ngữ tôi đã sử dụng với lambdas (JavaScript, Scheme và Haskell) mọi người có thể sẽ cho bạn lời khuyên ngược lại. Tôi nghĩ rằng điều đó chỉ cho thấy phong cách tốt phụ thuộc vào ngôn ngữ.
Tikhon Jelvis

nó nói với bạn kiểu gì? chắc chắn bạn có thể rõ ràng về loại tham số lambdas nhưng nó không phổ biến để làm điều đó, và không được thực hiện trong tình huống này
jk.

1

với hai ví dụ bạn đưa ra, chúng khác nhau ở chỗ khi bạn nói

List.ForEach(Console.WriteLine) 

bạn thực sự đang nói với ForEach Loop để sử dụng phương thức WriteLine

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

đang thực sự xác định một phương thức mà foreach sẽ gọi và sau đó bạn đang nói với nó những gì cần xử lý ở đó.

Vì vậy, đối với một lớp lót đơn giản nếu phương thức bạn định gọi mang chữ ký giống như phương thức đã được gọi, tôi không muốn xác định lambda, tôi nghĩ nó dễ đọc hơn một chút.

đối với các phương pháp với lambdas không tương thích chắc chắn là một cách tốt để đi, giả sử chúng không quá phức tạp.


1

Có một lý do rất mạnh mẽ để thích dòng đầu tiên.

Mỗi đại biểu có một thuộc Targettính, cho phép các đại biểu tham chiếu đến các phương thức thể hiện, ngay cả khi thể hiện đã vượt quá phạm vi.

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;

Chúng tôi không thể gọi a1.WriteData();vì không a1có giá trị. Tuy nhiên, chúng ta có thể gọi ra actionủy nhiệm mà không gặp vấn đề gì và nó sẽ in 4, vì actiongiữ một tham chiếu đến thể hiện mà phương thức sẽ được gọi.

Khi các phương thức ẩn danh được truyền dưới dạng đại biểu trong ngữ cảnh cá thể, đại biểu vẫn sẽ giữ một tham chiếu đến lớp chứa, mặc dù điều đó không rõ ràng:

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

Trong trường hợp cụ thể này, thật hợp lý khi giả định rằng .ForEachkhông lưu trữ nội bộ của đại biểu, điều đó có nghĩa là trường hợp Containervà tất cả dữ liệu của nó vẫn được giữ lại. Nhưng không có sự bảo đảm về điều đó; phương thức tiếp nhận đại biểu có thể giữ cho đại biểu và trường hợp vô thời hạn.

Mặt khác, các phương thức tĩnh không có ví dụ để tham chiếu. Sau đây sẽ không có một tham chiếu ngầm đến trường hợp 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);
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.