CodeGym /Các khóa học /C# SELF /Biểu thức lambda với Func<...

Biểu thức lambda với Func, Action, Predicate

C# SELF
Mức độ, Bài học
Có sẵn

1. Giới thiệu

Bạn biết điều thú vị không? Biểu thức lambda tự nó — giống như công thức mà không có nồi. Nó mô tả phải làm gì, nhưng cần một "khung" để sống trong code. Trong C# có sẵn các delegate tổng quát để làm điều đó: Func, Action và Predicate.

Hãy tưởng tượng chúng như những khuôn sẵn cho lambda của bạn — chọn khuôn rồi đổ logic vào. Không cần tự tạo delegate riêng khi đã có sẵn mọi thứ cần thiết.

Một ít lịch sử

Ngày trước, khi cần truyền một hàm làm tham số, phải khai báo delegate riêng. Việc đó lâu và rườm rà, trông kiểu như:

delegate int Calculate(int x, int y);

Calculate adder = (a, b) => a + b;

Khi C# có Func, Action và Predicate, bạn có thể quên việc khai báo delegate thủ công trong 90% trường hợp. Bây giờ trông đơn giản và tổng quát hơn nhiều:

Func<int, int, int> adder = (a, b) => a + b;

2. Func<T, TResult> — hàm có trả về

Cú pháp và mục đích

Func — là delegate generic, nhận từ 0 đến 16 tham số và trả về một giá trị.

Func<int, int, int> sum = (x, y) => x + y;

Func<kiểu1, kiểu2, ..., kiểuN, TResult> — tất cả các tham số trước cái cuối cùng là arguments, cái cuối cùng là kiểu trả về.

Ví dụ

1. Tổng hai số

Func<int, int, int> sum = (x, y) => x + y;
Console.WriteLine(sum(3, 5)); // 8

2. Lấy bình phương một số

Func<int, int> square = x => x * x;
Console.WriteLine(square(4)); // 16

3. Không có tham số

Func<string> greet = () => "Privet, lambda!";
Console.WriteLine(greet());

Sơ đồ trực quan

Chữ ký Ví dụ Mô tả
Func<int, int>
x => x * 2
Nhận int, trả về int
Func<int, int, int>
(a, b) => a + b
Hai int, trả về int
Func<string>
() => "hi"
Không nhận gì, trả về chuỗi

Trông nó như thế nào trong ứng dụng của bạn

Giả sử trong mini-app của chúng ta (gọi là "Danh bạ người dùng"), có một list số — muốn áp dụng các xử lý khác nhau. Ví dụ, lấy bình phương hoặc tính tổng với một số cố định:

List<int> numbers = new() { 1, 2, 3, 4, 5 };

Func<int, int> square = x => x * x;
var squares = numbers.Select(square);
Console.WriteLine(string.Join(", ", squares)); // 1, 4, 9, 16, 25

3. Action<T> — hành động không trả về

Action — delegate tổng quát cho các phương thức làm gì đó (ví dụ, in ra màn hình), nhưng không trả về gì.

Có thể nhận từ 0 đến 16 tham số, nhưng luôn trả về void.

Ví dụ

1. In ra màn hình

Action<string> print = text => Console.WriteLine("Dannye: " + text);
print("Privet, mir!");

2. Hành động không tham số

Action greet = () => Console.WriteLine("Dobro pozhalovat!");
greet();

3. Hành động với nhiều tham số

Action<int, int> showSum = (a, b) => Console.WriteLine($"Summa: {a + b}");
showSum(2, 3); // Summa: 5

Sơ đồ trực quan

Chữ ký Ví dụ Mô tả
Action
() => ...
Không tham số, không trả về
Action<int>
x => ...
Một tham số
Action<int, string>
(x, s) => ...
Nhiều tham số

Trong ứng dụng của chúng ta

Thêm vào danh bạ người dùng một method để in tất cả tên:

List<string> names = new() { "Anna", "Boris", "Vika" };
Action<string> printName = name => Console.WriteLine("Polzovatel': " + name);

names.ForEach(printName);
// hoặc thế này: names.ForEach(name => Console.WriteLine(name));

4. Predicate<T> — có hay không?

Khi cần một hàm trả về chỉ true hoặc false cho một tham số, dùng Predicate<T>. Đây đơn giản là delegate nhận một tham số và trả về bool.

Predicate — chính thức là "cái để kiểm tra boolean" bọc cho lambda.

Ví dụ

1. Kiểm tra số có lớn hơn 5 không

Predicate<int> isGreaterThanFive = x => x > 5;

Console.WriteLine(isGreaterThanFive(3)); // false
Console.WriteLine(isGreaterThanFive(7)); // true

2. Dùng với phương thức List<T>.Find

List<int> values = new() { 2, 4, 7, 10 };
int found = values.Find(isGreaterThanFive); // dùng Predicate<int>
Console.WriteLine(found); // 7

3. Tất cả đã trưởng thành chưa?

List<int> ages = new() { 12, 19, 34 };

bool allAdults = ages.TrueForAll(age => age >= 18);
// TrueForAll nhận Predicate<int>

Khác biệt với Func<T, bool> ở chỗ nào?

Về thực tế thì chúng có thể thay thế cho nhau. Ngay cả tài liệu Microsoft cũng nói: "Predicate<T> — chỉ là Func<T, bool> cho các API đặc thù". Nhưng một số method trong thư viện chuẩn vẫn yêu cầu chính xác Predicate.

5. Cách lambda "khớp" với Func, Action, Predicate

Khi bạn viết lambda, C# phân tích: "Ồ, hình dạng của nó phù hợp delegate yêu cầu — có thể gán!"

Func<int, int> f1 = x => x * 2;
Action<string> a1 = text => Console.WriteLine(text);
Predicate<int> p1 = x => x < 10;

Ở đâu đâu cũng lambda! Nhưng bên dưới — là ba delegate khác nhau với chữ ký khác nhau.

Áp dụng trong mã "thực"

List<User> users = new() {
    new User("Anna", 24),
    new User("Boris", 17),
    new User("Vika", 31),
};

// Hàm trả về chỉ những người đủ 18 tuổi (Predicate<User>)
List<User> adults = users.FindAll(user => user.Age >= 18);
Console.WriteLine("Spisok vzroslykh: " + string.Join(", ", adults.Select(u => u.Name)));

Nếu muốn in tên tất cả người dùng bằng Action<User>:

users.ForEach(user => Console.WriteLine(user.Name));

Lấy tên của họ (Func<User, string>):

IEnumerable<string> names = users.Select(user => user.Name);

Bảng để dễ nhìn

Delegate Chữ ký Ví dụ lambda Nơi dùng
Func<T, U>
T → U
user => user.Name
Select, mọi chuyển đổi
Action<T>
T → void
user => Console.WriteLine(...)
ForEach, các phương thức hành động
Predicate<T>
T → bool
user => user.Age > 18
Find, Exists, các bộ lọc

6. Ví dụ với ứng dụng nội bộ: từng bước

Mình mở rộng nhỏ ứng dụng "Danh bạ người dùng". Giả sử chúng ta có class User:

public class User
{
    public string Name { get; }
    public int Age { get; }
    public bool IsActive { get; set; }

    public User(string name, int age)
    {
        Name = name;
        Age = age;
        IsActive = true;
    }
}

1. Func<User, bool> — kiểm tra user đã đủ tuổi chưa

Func<User, bool> isAdult = user => user.Age >= 18;

Dùng trong LINQ:

var adults = users.Where(isAdult);

2. Predicate<User> — tìm user đang active

Predicate<User> isActive = user => user.IsActive;
User found = users.Find(isActive);

3. Action<User> — deactivate user

Action<User> deactivate = user => user.IsActive = false;
users.ForEach(deactivate);

4. Func<User, string> — lấy mô tả ngắn

Func<User, string> describe = user => $"{user.Name} ({user.Age})";
var descriptions = users.Select(describe);

Tất cả những lambda này — là "code dưới dạng data", có thể truyền vào method, lưu trong biến, kết hợp với nhau.

7. Những điểm không rõ ràng và lỗi thường gặp

1. Lambda phải phù hợp chữ ký của delegate. Nếu chữ ký không khớp sẽ lỗi biên dịch.

Func<int, string> wrong = x => x * 2; // lỗi: mong đợi string, nhận int
// Đúng:
Func<int, string> right = x => (x * 2).ToString();

2. Đừng quên về void vs return. Action không trả về giá trị — cố gắng viết kiểu Action<int> a = x => x * x; sẽ không được vì có giá trị trả về trong khi không nên có.

3. Predicate<T> và Func<T, bool> thường có thể thay thế nhau, nhưng không phải lúc nào. Đôi khi các method collection yêu cầu chính xác Predicate<T>, đôi khi yêu cầu Func<T, bool>. Gán trực tiếp đôi khi không chạy được nếu không bọc rõ ràng.

Predicate<int> pred = x => x > 0;
Func<int, bool> func = pred; // lỗi
// Nhưng:
Func<int, bool> func2 = x => x > 0;
Predicate<int> pred2 = new Predicate<int>(func2); // có thể chuyển qua constructor
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Sử dụng `Action` để xử lý danh sách chuỗi
Sử dụng `Action` để xử lý danh sách chuỗi
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION