Biểu thức C # Lambda: Tại sao tôi nên sử dụng chúng?


309

Tôi đã nhanh chóng đọc qua tài liệu Microsoft Lambda Expression .

Mặc dù vậy, loại ví dụ này đã giúp tôi hiểu rõ hơn:

delegate int del(int i);
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25

Tuy nhiên, tôi không hiểu tại sao đó là một sự đổi mới. Nó chỉ là một phương thức chết khi "biến phương thức" kết thúc, phải không? Tại sao tôi nên sử dụng điều này thay vì một phương pháp thực sự?


3
Đối với những người bạn đã đến trang này và không biết a delegatelà gì trong C #, tôi khuyên bạn nên đọc phần này trước khi đọc phần còn lại của trang này: stackoverflow.com/questions/2082615/
Kolob Canyon

Câu trả lời:


281

Biểu thức Lambda là một cú pháp đơn giản hơn cho các đại biểu ẩn danh và có thể được sử dụng ở mọi nơi mà một đại biểu ẩn danh có thể được sử dụng. Tuy nhiên, điều ngược lại là không đúng sự thật; Các biểu thức lambda có thể được chuyển đổi thành các cây biểu thức cho phép thực hiện nhiều phép thuật như LINQ sang SQL.

Sau đây là một ví dụ về biểu thức LINQ to Object bằng cách sử dụng các đại biểu ẩn danh sau đó biểu thức lambda để cho thấy chúng dễ nhìn hơn bao nhiêu:

// anonymous delegate
var evens = Enumerable
                .Range(1, 100)
                .Where(delegate(int x) { return (x % 2) == 0; })
                .ToList();

// lambda expression
var evens = Enumerable
                .Range(1, 100)
                .Where(x => (x % 2) == 0)
                .ToList();

Các biểu thức Lambda và các đại biểu ẩn danh có lợi thế hơn khi viết một hàm riêng biệt: chúng thực hiện các bao đóng có thể cho phép bạn chuyển trạng thái cục bộ cho hàm mà không cần thêm tham số cho hàm hoặc tạo các đối tượng sử dụng một lần.

Cây biểu hiện là một tính năng mới rất mạnh mẽ của C # 3.0, cho phép API xem xét cấu trúc của biểu thức thay vì chỉ tham chiếu đến một phương thức có thể được thực thi. API chỉ cần tạo tham số ủy nhiệm thành Expression<T>tham số và trình biên dịch sẽ tạo cây biểu thức từ lambda thay vì ủy nhiệm ẩn danh:

void Example(Predicate<int> aDelegate);

gọi như:

Example(x => x > 5);

trở thành:

void Example(Expression<Predicate<int>> expressionTree);

Cái sau sẽ được thông qua một đại diện của cây cú pháp trừu tượng mô tả biểu thức x > 5. LINQ to SQL dựa vào hành vi này để có thể biến các biểu thức C # thành các biểu thức SQL mong muốn để lọc / đặt hàng / vv ở phía máy chủ.


1
Nếu không có các bao đóng, bạn có thể sử dụng các phương thức tĩnh như các cuộc gọi lại, nhưng bạn vẫn phải xác định các phương thức đó trong một số lớp, gần như chắc chắn sẽ tăng phạm vi của phương thức đó ngoài mục đích sử dụng.
ĐK.

10
FWIW, bạn có thể đóng cửa với một đại biểu ẩn danh, vì vậy bạn không thực sự cần lambdas cho điều đó. Lambdas chỉ dễ đọc hơn các đại biểu ẩn danh, mà không sử dụng Linq sẽ khiến mắt bạn chảy máu.
Stewol

138

Các hàm và biểu thức ẩn danh rất hữu ích cho các phương thức một lần không được hưởng lợi từ công việc bổ sung cần thiết để tạo một phương thức đầy đủ.

Xem xét ví dụ này:

 string person = people.Find(person => person.Contains("Joe"));

đấu với

 public string FindPerson(string nameContains, List<string> persons)
 {
     foreach (string person in persons)
         if (person.Contains(nameContains))
             return person;
     return null;
 }

Đây là tương đương về chức năng.


8
Phương thức Find () được định nghĩa như thế nào để xử lý biểu thức lambda này?
Patrick Desjardins

3
Vị ngữ <T> là những gì phương thức Tìm kiếm đang mong đợi.
Darren Kopp

1
Vì biểu thức lambda của tôi khớp với hợp đồng dành cho Vị ngữ <T>, nên phương thức Find () chấp nhận nó.
Joseph Daigle

bạn có nghĩa là "chuỗi người = người.Find (người => người.Contains (" Joe "));"
Gern Blanston

5
@FKCoder, không, anh ấy không, mặc dù có thể đã rõ ràng hơn nếu anh ấy nói "chuỗi người = người.Find (p => p.Contains (" Joe "));"
Stewol

84

Tôi thấy chúng hữu ích trong một tình huống khi tôi muốn khai báo một trình xử lý cho một số sự kiện của điều khiển, sử dụng một điều khiển khác. Để làm điều đó một cách bình thường, bạn sẽ phải lưu trữ các tham chiếu của các điều khiển trong các trường của lớp để bạn có thể sử dụng chúng theo một phương thức khác với cách chúng được tạo.

private ComboBox combo;
private Label label;

public CreateControls()
{
    combo = new ComboBox();
    label = new Label();
    //some initializing code
    combo.SelectedIndexChanged += new EventHandler(combo_SelectedIndexChanged);
}

void combo_SelectedIndexChanged(object sender, EventArgs e)
{
    label.Text = combo.SelectedValue;
}

nhờ biểu thức lambda bạn có thể sử dụng nó như thế này:

public CreateControls()
{
    ComboBox combo = new ComboBox();
    Label label = new Label();
    //some initializing code
    combo.SelectedIndexChanged += (s, e) => {label.Text = combo.SelectedValue;};
}

Dễ dàng hơn nhiều.


Trong ví dụ đầu tiên, tại sao không bỏ người gửi và nhận giá trị?
Andrew

@Andrew: Trong ví dụ đơn giản này, không cần thiết phải sử dụng người gửi, bởi vì chỉ có một thành phần trong câu hỏi và sử dụng trường trực tiếp lưu một diễn viên, giúp cải thiện sự rõ ràng. Trong một kịch bản trong thế giới thực, cá nhân tôi cũng thích sử dụng người gửi hơn. Thông thường tôi sử dụng một trình xử lý sự kiện cho một số sự kiện, nếu có thể và vì vậy tôi phải xác định người gửi thực tế.
Chris Tophski

35

Lambda đã dọn sạch cú pháp đại biểu ẩn danh của C # 2.0 ... chẳng hạn

Strings.Find(s => s == "hello");

Đã được thực hiện trong C # 2.0 như thế này:

Strings.Find(delegate(String s) { return s == "hello"; });

Về mặt chức năng, họ làm điều tương tự chính xác, đó chỉ là một cú pháp ngắn gọn hơn nhiều.


3
Chúng không hoàn toàn giống nhau - như @Neil Williams chỉ ra, bạn có thể trích xuất AST của lambdas bằng cây biểu thức, trong khi các phương thức ẩn danh có thể được sử dụng theo cách tương tự.
LJS

đây là một trong nhiều ưu điểm khác của lambda. Nó giúp hiểu mã tốt hơn các phương thức ẩn danh. chắc chắn đó không phải là ý định tạo ra lambdas nhưng đây là những kịch bản mà nó có thể được sử dụng thường xuyên hơn.
Guruji

29

Đây chỉ là một cách sử dụng biểu thức lambda. Bạn có thể sử dụng biểu thức lambda ở bất cứ đâu bạn có thể sử dụng một đại biểu. Điều này cho phép bạn làm những việc như thế này:

List<string> strings = new List<string>();
strings.Add("Good");
strings.Add("Morning")
strings.Add("Starshine");
strings.Add("The");
strings.Add("Earth");
strings.Add("says");
strings.Add("hello");

strings.Find(s => s == "hello");

Mã này sẽ tìm kiếm danh sách cho một mục phù hợp với từ "xin chào". Cách khác để làm điều này là thực sự chuyển một đại biểu cho phương thức Tìm, như thế này:

List<string> strings = new List<string>();
strings.Add("Good");
strings.Add("Morning")
strings.Add("Starshine");
strings.Add("The");
strings.Add("Earth");
strings.Add("says");
strings.Add("hello");

private static bool FindHello(String s)
{
    return s == "hello";
}

strings.Find(FindHello);

BIÊN TẬP :

Trong C # 2.0, điều này có thể được thực hiện bằng cú pháp đại biểu ẩn danh:

  strings.Find(delegate(String s) { return s == "hello"; });

Lambda đã làm sạch đáng kể cú pháp đó.


2
@Jonathan Holland: Cảm ơn bạn đã chỉnh sửa và thêm cú pháp đại biểu ẩn danh. Nó hoàn thành ví dụ độc đáo.
Scott Dorman

Đại biểu ẩn danh là gì? // xin lỗi tôi mới biết về c #
HackerMan

1
@HackerMan, hãy nghĩ về một đại biểu ẩn danh là một chức năng không có "tên". Bạn vẫn xác định một chức năng, có thể có đầu vào và đầu ra, nhưng vì đó là tên mà bạn không thể tham khảo trực tiếp. Trong mã được hiển thị ở trên, bạn đang xác định một phương thức (lấy a stringvà trả về a bool) làm tham số cho Findchính phương thức đó.
Scott Dorman

22

Microsoft đã cho chúng ta một cách sạch hơn, thuận tiện hơn để tạo các đại biểu ẩn danh được gọi là biểu thức Lambda. Tuy nhiên, không có nhiều sự chú ý được trả cho phần biểu thức của tuyên bố này. Microsoft đã phát hành toàn bộ không gian tên, System.Linq.Expressions , chứa các lớp để tạo cây biểu thức dựa trên biểu thức lambda. Cây biểu hiện được tạo thành từ các đối tượng đại diện cho logic. Ví dụ: x = y + z là một biểu thức có thể là một phần của cây biểu thức trong .Net. Hãy xem xét ví dụ (đơn giản) sau:

using System;
using System.Linq;
using System.Linq.Expressions;


namespace ExpressionTreeThingy
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<int, int>> expr = (x) => x + 1; //this is not a delegate, but an object
            var del = expr.Compile(); //compiles the object to a CLR delegate, at runtime
            Console.WriteLine(del(5)); //we are just invoking a delegate at this point
            Console.ReadKey();
        }
    }
}

Ví dụ này là tầm thường. Và tôi chắc chắn rằng bạn đang nghĩ, "Điều này là vô ích vì tôi có thể trực tiếp tạo ra đại biểu thay vì tạo một biểu thức và biên dịch nó khi chạy". Và bạn sẽ đúng. Nhưng điều này cung cấp nền tảng cho cây biểu hiện. Có một số biểu thức có sẵn trong các không gian tên Biểu thức và bạn có thể tạo biểu thức của riêng mình. Tôi nghĩ rằng bạn có thể thấy rằng điều này có thể hữu ích khi bạn không biết chính xác thuật toán nên có trong thời gian thiết kế hoặc biên dịch. Tôi đã thấy một ví dụ ở đâu đó để sử dụng điều này để viết một máy tính khoa học. Bạn cũng có thể sử dụng nó cho các hệ thống Bayes hoặc để lập trình di truyền(AI). Một vài lần trong sự nghiệp, tôi đã phải viết chức năng giống như Excel cho phép người dùng nhập các biểu thức đơn giản (phép cộng, phép trừ, v.v.) để hoạt động trên dữ liệu có sẵn. Trong pre-.Net 3.5, tôi đã phải sử dụng một số ngôn ngữ kịch bản bên ngoài C # hoặc phải sử dụng chức năng phát mã trong phản chiếu để tạo mã .Net một cách nhanh chóng. Bây giờ tôi sẽ sử dụng cây biểu hiện.


12

Nó tiết kiệm việc phải có các phương thức chỉ được sử dụng một lần ở một nơi cụ thể khỏi việc được xác định ở xa nơi chúng được sử dụng. Việc sử dụng tốt là làm công cụ so sánh cho các thuật toán chung như sắp xếp, trong đó bạn có thể xác định hàm sắp xếp tùy chỉnh nơi bạn đang gọi sắp xếp thay vì xa hơn buộc bạn phải tìm ở nơi khác để xem bạn đang sắp xếp thứ gì.

Và nó không thực sự là một sự đổi mới. LISP đã có chức năng lambda trong khoảng 30 năm trở lên.


6

Bạn cũng có thể tìm thấy việc sử dụng các biểu thức lambda trong việc viết mã chung để hành động theo các phương thức của bạn.

Ví dụ: Hàm chung để tính thời gian thực hiện bởi một cuộc gọi phương thức. (tức là Actionở đây)

public static long Measure(Action action)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    action();
    sw.Stop();
    return sw.ElapsedMilliseconds;
}

Và bạn có thể gọi phương thức trên bằng cách sử dụng biểu thức lambda như sau,

var timeTaken = Measure(() => yourMethod(param));

Biểu thức cho phép bạn nhận được giá trị trả về từ phương thức của bạn và cả param

var timeTaken = Measure(() => returnValue = yourMethod(param, out outParam));

5

Biểu thức Lambda là một cách súc tích để thể hiện một phương thức ẩn danh. Cả hai phương thức ẩn danh và biểu thức Lambda đều cho phép bạn xác định nội tuyến thực hiện phương thức, tuy nhiên, một phương thức ẩn danh rõ ràng yêu cầu bạn xác định các loại tham số và kiểu trả về cho một phương thức. Biểu thức Lambda sử dụng tính năng suy luận kiểu của C # 3.0, cho phép trình biên dịch suy ra loại biến dựa trên ngữ cảnh. Nó rất thuận tiện vì điều đó giúp chúng ta tiết kiệm rất nhiều việc đánh máy!


5

Một biểu thức lambda giống như một phương thức ẩn danh được viết thay cho một thể hiện của đại biểu.

delegate int MyDelagate (int i);
MyDelagate delSquareFunction = x => x * x;

Hãy xem xét biểu thức lambda x => x * x;

Giá trị tham số đầu vào là x (ở phía bên trái của =>)

Logic hàm là x * x (ở bên phải của =>)

Mã của biểu thức lambda có thể là một khối câu lệnh thay vì một biểu thức.

x => {return x * x;};

Thí dụ

Lưu ý: Funclà một đại biểu chung được xác định trước.

    Console.WriteLine(MyMethod(x => "Hi " + x));

    public static string MyMethod(Func<string, string> strategy)
    {
        return strategy("Lijo").ToString();
    }

Người giới thiệu

  1. Làm thế nào một đại biểu và giao diện có thể được sử dụng thay thế cho nhau?

4

Rất nhiều lần, bạn chỉ sử dụng chức năng ở một nơi, do đó, thực hiện một phương thức chỉ làm xáo trộn lớp học.


3

Đó là một cách để thực hiện thao tác nhỏ và đặt nó rất gần với nơi nó được sử dụng (không giống như khai báo một biến gần với điểm sử dụng của nó). Điều này được cho là để làm cho mã của bạn dễ đọc hơn. Bằng cách ẩn danh biểu thức, bạn cũng sẽ khiến người khác khó phá vỡ mã máy khách của mình hơn nếu chức năng này được sử dụng ở nơi khác và được sửa đổi để "nâng cao" nó.

Tương tự, tại sao bạn cần sử dụng foreach? Bạn có thể thực hiện mọi thứ trong foreach với một vòng lặp đơn giản hoặc chỉ cần sử dụng IEnumerable trực tiếp. Trả lời: bạn không cần nó nhưng nó làm cho mã của bạn dễ đọc hơn.


0

Sự đổi mới là trong loại an toàn và minh bạch. Mặc dù bạn không khai báo các loại biểu thức lambda, chúng được suy ra và có thể được sử dụng bởi tìm kiếm mã, phân tích tĩnh, công cụ tái cấu trúc và phản ánh thời gian chạy.

Ví dụ: trước khi bạn có thể đã sử dụng SQL và có thể bị tấn công SQL SQL, bởi vì tin tặc đã vượt qua một chuỗi trong đó một số thường được mong đợi. Bây giờ bạn sẽ sử dụng biểu thức lambda LINQ, được bảo vệ khỏi đó.

Việc xây dựng API LINQ trên các đại biểu thuần túy là không thể, bởi vì nó yêu cầu kết hợp các cây biểu thức với nhau trước khi đánh giá chúng.

Trong năm 2016, hầu hết các ngôn ngữ phổ biến đều có hỗ trợ biểu thức lambda và C # là một trong những ngôn ngữ tiên phong trong sự phát triển này trong số các ngôn ngữ mệnh lệnh chính thống.


0

Đây có lẽ là lời giải thích tốt nhất về lý do nên sử dụng biểu thức lambda -> https://youtu.be/j9nj5dTo54Q

Tóm lại, đó là để cải thiện khả năng đọc mã, giảm khả năng xảy ra lỗi bằng cách sử dụng lại thay vì sao chép mã và tận dụng tối ưu hóa xảy ra đằng sau hậu trường.


0

Lợi ích lớn nhất của biểu thức lambda và các hàm ẩn danh là việc chúng cho phép máy khách (lập trình viên) của thư viện / khung công tác tiêm chức năng bằng mã trong thư viện / khung đã cho (vì đó là LINQ, ASP.NET Core và nhiều cách khác) theo cách mà các phương thức thông thường không thể. Tuy nhiên, sức mạnh của chúng không rõ ràng đối với một lập trình viên ứng dụng mà là một người tạo ra các thư viện mà sau này sẽ được sử dụng bởi những người khác muốn cấu hình hành vi của mã thư viện hoặc người sử dụng thư viện. Vì vậy, bối cảnh sử dụng hiệu quả biểu thức lambda là việc sử dụng / tạo thư viện / khung.

Ngoài ra, vì họ mô tả mã sử dụng một lần, họ không phải là thành viên của một lớp nơi điều đó sẽ dẫn đến sự phức tạp của mã hơn. Hãy tưởng tượng phải khai báo một lớp với tiêu điểm không rõ ràng mỗi khi chúng ta muốn cấu hình hoạt động của một đối tượng lớp.

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.