C# – Tìm hiểu về Lambda Expression (Biểu thức Lambda)

So với anonymous method, lambda expression được coi là một sự  cái tiến đáng giá từ  các phiên bản. Khi dùng anonymous method, bạn có thể tạo các hàm in-line nhằm hạn chế việc khai báo các hàm riêng lẻ không cần thiết, giúp mã lệnh ngắn gọn hơn. Nay với lambda expression, bạn có thể viết ngắn gọn và dễ dàng hơn nhờ việc cung cấp toán tử và cú pháp mới, đồng thời thể hiện sự “thông minh” của compiler bằng cách tự nhận diện kiểu của dữ liệu.

1. Giới thiệu

Nếu chưa có khái niệm về gì về anonymous method, bạn có thể đọc bài hướng dẫn của tôi tại đây, việc này sẽ giúp bạn tiếp cận dễ dàng hơn những vấn đề sẽ được trình bày trong bài này. Ta có thể hình dung sự cải tiến kĩ thuật từ delegate đến lambda expression theo 3 bước:

–         Delegate

–          Anonymous method

–          Lambda expression

Vì thế hãy chuẩn bị những kiến thức nền tảng trước khi tiếp tục

2. Toán t Lambda:

Ta sẽ lấy một ví dụ tương tự trong phần bài anonymous method trước để bắt đầu. Bạn có một mảng các số nguyên kiểu int sau:

int[] numbers = { 10, 4, 3, 2, 8, 6, 5, 7, 9, 1 };

Ví dụ 1:
Bây giờ ta sẽ dùng anonymous method để in các phần tử trong mảng trên ra. Ta dùng phương thức tĩnh Array.ForEeach(T[] array, Action action), ở đây có thể bỏ đi phần định kiểu T. Compiler sẽ tự động hiểu và xác định kiểu dựa vào kiểu của tham số bạn truyền vào. Cụ thể ta viết như sau:

C# 2.0:

Array.ForEach(numbers, delegate(int x){
Console.WriteLine(x);
});

Trong C# 3.0 ta có thể dùng cú pháp ngắn gọn hơn nữa để thực hiện điều này với cách hoạt động tương tự như với anonymous method. Ta viết lại đoạn mã trên như sau:

C# 3.0:

Array.ForEach(numbers, (int x) => { Console.WriteLine(x); });

Nếu chỉ có một lệnh cần thực thi ta có thể bỏ cặp dấu ngoặc nhọn đi “{}”, và trong hầu hết trường hợp, compiler có thể đoán ra được kiểu dữ liệu của biến nên trong trường hợp này ta rút gọn lại như sau:

Array.ForEach(numbers, (x) => Console.WriteLine(x) );

Nếu chỉ có một tham số bạn có thể bỏ cặp dấu ngoặc đơn đi:

Array.ForEach(numbers, x => Console.WriteLine(x) );

Rõ ràng câu lệnh trên được đơn giản hóa khá nhiều so với ban đầu. Bạn có thể thấy điểm khác biệt là phần tham số và lệnh thực thi được ngăn cách với nhau bởi kí hiệu =>. Kí hiệu => này được gọi là toán tử lambda. Ta có dạng thức cơ bản của lambda expression như sau:

(parameter-list) =>  expression

Nếu không có tham số thì cú pháp sẽ như sau:

() => expression

Bây giờ ta thử làm một vài ví dụ đơn giản nữa để giúp bạn làm quen với kĩ thuật này. Mỗi ví dụ sẽ được viết với một phiên bản dùng anonymous method và sau đó là phiên bản dùng lambda expression để bạn dễ so sánh.

Ví dụ 2:

Ta sẽ sắp xếp mảng numbers trên theo thứ tự giảm dần. Ta sẽ sắp xếp bằng phương thức Array.Sort(T[] array, Comparisioncomparision).

Delegate Comparisionyêu cầu 2 tham số và trả về kiểu số tương tự như phương thức CompareTo() dùng để so sánh hai đối tượng (bạn có thể dùng phép trừ ‘-‘ để thay thế, để tường minh hơn nên tôi dùng CompareTo() để so sánh).

Ta có hai phiên bản để thực hiện yêu cầu này:

– Anonymous method:

    Array.Sort(numbers, delegate(int x, int y)

    {

    return y.CompareTo(x);

    });

-Lambda expression:

Array.Sort(numbers, (x,y) => y.CompareTo(x));

Ví dụ này tương tự như ví dụ đầu tiên trong bài, ngoại trừ việc nó phải return về một giá trị để so sánh. Như bạn thấy, lambda expression không cần dùng bất kì kí hiệu hay từ khóa gì để cho thấy rằng nó trả về một giá trị. Ta chỉ có thể nhận biết điều này dựa vào kết quả mà các biểu thức phía sau toán tử => trả về. Theo một cách hiểu khác, lambda expression luôn ngầm định trả về một giá trị như là void, int,…

Bạn có thể chỉ rõ việc trả về của các biểu thức sau toán tử => với từ khóa return, tuy nhiên nhớ dùng cặp ngoặc ‘{}’ để bao chúng lại như trong anonymous method.
Ví dụ 3:
Vấn sử dụng các phương thức tĩnh trong lớp Array, ta sẽ kiểm tra xem các phần tử trong mảng numbers có tuân theo một quy luật nào đó không bằng cách sử dụng phương thức Array.TrueForAll(T[] array, Predicate match). Phương thức này sẽ kiểm tra tất cả các phần tử dựa vào quy tắc của delegate Predicate mà ta truyền vào. Nếu tất cả các phần tử trong mảng đều khớp thì sẽ trả về true, ngược lại sẽ trả về false nếu có ít nhất một phần tử không khớp. Ta có hai phiên bản sau:

– Anonymous method:

bool b=Array.TrueForAll(numbers, delegate(int x){

    return x

-Lambda expression:

bool b = Array.TrueForAll(numbers, x => x < 11);

Quy tắc so sánh ta dùng ở đây là mỗi phần tử phải bé hơn 11. Dĩ nhiên kết quả của phương thức TrueForAll() sẽ luôn trả về true vì như bạn thấy các phần tử của mảng numbers chỉ có 10 là lớn nhất. Bạn có thể in biến b ra để xác thực kết quả này.
3. Delegate Func:

Trong phiên bản C# 3.0, đi kèm với việc ra đời của lambda expression, Microsoft cung cấp cho ta một kiểu delegate mới linh hoạt và tiện dụng hơn, tên của kiểu delegate này là Func.

Func cho phép khai báo và tạo ra các dạng delegate với số lượng tham số và kiểu trả về khác nhau, tương tự như khi bạn tạo ra một phương thức.

Func được dùng chủ yếu để tạo và lưu trữ một anonymous method ngắn gọn bằng lambda expression và được sử dụng như những phương thức thông thường. Cú pháp để sử dụng Func là viết các kiểu của tham số và giá trị trả về vào cặp ngoặc ‘<>’, theo sau từ khóa Func.

Func

Ở đây T là các kiểu của tham số cần truyền vào và TResult là kiểu của giá trị trả về. Chú ý là Func yêu cầu ít nhất một tham sô trong cặp ‘<>’, tức là phải có kiểu trả về. Bạn không để đặt void hay để một cặp ngoặc ‘<>’ rỗng khi dùng Func.

Một số ví dụ về khai báo Func:

+Yêu cầu một tham số kiểu string và trả về một giá trị int: lấy chiều dài của chuỗi.

    Func stringLength = s => s.Length;

    Console.WriteLine(stringLength (“yinyang”));

+Yêu cầu 1 tham số float, 1 tham số và trả về kiểu float: tính tích của 2 số

    Func multiply = (x, y) => x * y;

    Console.WriteLine(multiply (9f, 7));

+ Trả về max của 2 số:

    Func max = (x, y) => x>y?x:y;

    Console.WriteLine(max(9, 7));

Như bạn thấy, nếu dùng anonymous method cho các ví dụ trên, vì là phương thức in-line nên bạn chỉ được gọi một lần duy nhất khi định nghĩa, muốn gọi ở nơi khác trong cùng một phương thức bạn phải định nghĩa lại. Nhưng ở đây Func cung cấp một cách thức để lưu trữ anonymous method cho phép bạn sử dụng nhiều lần mà không cần phải khai báo và định nghĩa phương thức bên ngoài, và tất nhiên là với cú pháp ngắn gọn hơn so với kiểu truyền thống.
4. Phần kết

Như vậy ta đã đi qua phần giới thiệu và cách sử dụng về lambda expression. Trong bài viết này, bạn đã biết được sự khác nhau giữa lambda expression với anonymous method và delegate, đồng thời thấy được sự tiện dụng của nó trong những ngữ cảnh cụ thể.