Biểu thức Lambda
Giới thiệu biểu thức Lambdas
Biểu thức lambda là một phương thức nặc danh (anonymous function) mà bạn có thể sử dụng để tạo ra các delegate hoặc các cây biểu thức (expression tree). Bằng cách sử dụng các biểu thức lambda, bạn có thể viết các hàm cục bộ có thể được truyền như các đối số hoặc trả về như là giá trị của hàm gọi. Các biểu thức lambda đặc biệt hữu ích cho việc viết các biểu thức truy vấn LINQ.
Để tạo ra một biểu thức lambda, bạn chỉ định các tham số đầu vào (nếu có) ở phía bên trái của toán tử lambda =>
, và bạn đặt biểu thức hoặc khối câu lệnh ở phía bên kia. Ví dụ, biểu thức lambda x => x * x
chỉ định một tham số có tên x và trả về giá trị của x bình phương. Bạn có thể gán biểu thức này cho một kiểu delegate, như ví dụ sau đây:
delegate int del(int i); static void Main(string[] args) { del myDelegate = x => x * x; int j = myDelegate(5); //j = 25 }
Tạo cây biểu thức:
using System.Linq.Expressions; namespace LambdasDemo { class Program { static void Main(string[] args) { Expression<del> myET = x => x * x; } } }
Toán tử =>
có cùng độ ưu tiên như công thức (=).
Lambdas được sử dụng trong các truy vấn LINQ dựa trên phương pháp như các đối số cho các phương thức điều khiển truy vấn chuẩn như Where.
Khi bạn sử dụng cú pháp dựa trên phương pháp để gọi phương thức Where trong lớp Enumerable (như bạn làm trong LINQ to Objects và LINQ to XML) tham số này là một loại đại biểu System.Func <T, TResult>. Biểu thức lambda là cách thuận tiện nhất để tạo ra delegate đó. Khi bạn gọi phương thức tương tự trong, ví dụ, lớp System.Linq.Queryable (như bạn làm trong LINQ to SQL) thì kiểu tham số là một System.Linq.Expressions.Expression nơi Func là bất kỳ delegate Func nào với đến mười sáu tham số đầu vào. Một lần nữa, biểu thức lambda chỉ là một cách ngắn gọn để xây dựng cây biểu thức đó. Các lambdas cho phép gọi các Where để trông giống nhau mặc dù trên thực tế các loại đối tượng được tạo ra từ lambda là khác nhau.
Trong ví dụ trước, chú ý rằng chữ ký đại diện có một tham số đầu vào ngầm định của kiểu int, và trả về một int. Biểu thức lambda có thể được chuyển thành một delegate của kiểu đó bởi vì nó cũng có một tham số đầu vào (x) và một giá trị trả về mà trình biên dịch có thể ngầm chuyển thành kiểu int. (Loại suy luận được thảo luận chi tiết hơn trong các phần sau) Khi delegate được gọi bằng cách sử dụng một tham số đầu vào là 5, nó trả về kết quả là 25.
Lambdas không được phép ở phía bên trái của hoặc là nhà điều hành.
Tất cả các hạn chế áp dụng cho các phương pháp nặc danh cũng áp dụng cho biểu thức lambda.
Khai báo biểu thức Lambdas
Một biểu thức lambda với một biểu thức ở phía bên phải của toán tử => được gọi là một biểu thức lambda. Biểu thức lambdas được sử dụng rộng rãi trong việc xây dựng cây biểu thức. Một biểu thức lambda trả về kết quả của biểu thức và có dạng cơ bản sau đây:
(input-parameters) => expression
Dấu ngoặc đơn được yêu cầu khi có từ hai tham số đầu vào trở lên. Ví dụ:
(x, y) => x == y
Trong trường hợp trình biên dịch gặp khó khăn trong việc suy diễn kiểu thì cần chỉ ra kiểu một cách tường minh cho các tham số. Ví dụ:
(int x, string s) => s.Length > x
Thỉnh thoảng danh sách tham số là rỗng:
() => SomeMethod()
Câu lệnh Lambdas
Một câu lệnh lambda giống với một biểu thức lambda ngoại trừ các lệnh (s) được kèm theo trong dấu ngoặc:
(danh sách tham số đầu vào) => {câu lệnh; }
Phần thân của một câu lệnh lambda có thể bao gồm một số các câu lệnh; tuy nhiên, trong thực tế thường không có nhiều hơn hai hoặc ba.
delegate void TestDelegate(string s); TestDelegate del = n => { string s = n + " World"; Console.WriteLine(s); };
Câu lệnh lambdas, như các phương thức nặc danh (anonymous methods), không thể được sử dụng để tạo các cây biểu thức.
Biểu thức Lambda với các toán tử truy vấn chuẩn.
Nhiều toán tử truy vấn chuẩn có một tham số đầu vào có kiểu là một trong những nhóm của Func <T, TResult> đại diện chung. Các delegate này sử dụng các tham số kiểu để xác định số lượng và loại các tham số đầu vào, và kiểu trả về của delegate. Các delegate Func rất hữu ích cho việc đóng gói các biểu thức do người dùng định nghĩa được áp dụng cho mỗi phần tử trong một bộ dữ liệu nguồn. Ví dụ: hãy xem xét delegate sau:
public delegate TResult Func<TArg0, TResult>(TArg0 arg0)
Các delegate có thể được khởi tạo như Func <int, bool> myFunc nơi int là một tham số đầu vào và bool là giá trị trả về. Giá trị trả về luôn được xác định trong tham số loại cuối cùng. Func <int, string, bool> định nghĩa một delegate với hai tham số đầu vào, int và string, và một kiểu trả về bool. Delegate Func sau, khi được gọi, sẽ trả về true hoặc false để cho biết liệu tham số đầu vào bằng 5:
Func<int, bool> myFunc = x => x == 5; bool result = myFunc(4); // returns false of course
Bạn cũng có thể cung cấp một biểu thức lambda khi kiểu đối số là một Expression <Func>, ví dụ trong các toán tử truy vấn chuẩn được định nghĩa trong System.Linq.Queryable. Khi bạn chỉ định một đối số Expression <Func>, lambda sẽ được biên dịch thành cây biểu thức.
Một toán tử truy vấn chuẩn, phương pháp Count, được hiển thị ở đây:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int oddNumbers = numbers.Count(n => n % 2 == 1);
Trình biên dịch có thể suy ra kiểu của tham số đầu vào, hoặc bạn cũng có thể chỉ định nó một cách rõ ràng. Biểu thức lambda đặc biệt này đếm những số nguyên (n) lẻ.
Dòng lệnh sau đây tạo ra một dãy có chứa tấL cả các phần tử trong mảng số nằm phía trái của 9 vì đó là số đầu tiên trong chuỗi không thỏa mãn điều kiện:
var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);
Ví dụ này cho thấy làm thế nào để xác định nhiều tham số đầu vào bằng sử dụng ngoặc đơn. Phương thức trả về tất cả các phần tử trong mảng số cho đến khi gặp một số có giá trị nhỏ hơn vị trí của nó. Đừng nhầm lẫn toán tử lambda (=>) với toán tử lớn hơn hoặc bằng (> =).
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
Suy diễn kiểu trong Lambda
Khi viết lambdas, bạn thường không phải chỉ định một loại cho các tham số đầu vào vì trình biên dịch có thể suy ra kiểu dựa trên cơ sở lambda, kiểu delegate của tham số và các yếu tố khác như được mô tả trong C # Language Specification. Đối với hầu hết các toán tử truy vấn chuẩn, đầu vào đầu tiên là loại của các phần tử trong chuỗi nguồn. Vì vậy, nếu bạn đang truy vấn một khách hàng <ie> IEnumerable, thì biến đầu vào được suy ra là một đối tượng Customer, có nghĩa là bạn có quyền truy cập vào các phương thức và thuộc tính của nó:
customers.Where(c => c.City == "London");
Các quy tắc chung cho lambdas như sau:
- Lambda phải chứa cùng một số tham số như kiểu delegate.
- Mỗi tham số đầu vào trong lambda phải được chuyển đổi ngầm tới tham số delegate tương ứng của nó.
- Giá trị trả lại của lambda (nếu có) phải được chuyển đổi ngầm cho kiểu trả về của delegate.
Lưu ý rằng các biểu thức lambda tự chúng không có một loại bởi vì hệ thống kiểu thông thường không có khái niệm nội tại về "biểu thức lambda". Tuy nhiên đôi khi rất thuận tiện để nói chuyện về "kiểu" của một biểu thức lambda. Trong những trường hợp này, kiểu này đề cập đến loại delegate hoặc expression mà biểu thức lambda được chuyển đổi.