CodeGym /Các khóa học /C# SELF /Làm quen với phương thức ẩn danh (

Làm quen với phương thức ẩn danh ( delegate)

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

1. Giới thiệu

Khi bạn lập trình, đôi khi cần mô tả một hành động đơn giản, chỉ dùng một lần và sẽ không dùng ở đâu khác. Tạo một phương thức riêng cho việc đó — giống như đóng đinh bằng búa trong bộ lắp ghép máy bay. Dễ hơn nhiều là dùng thứ tiện tay và giải quyết nhiệm vụ "tại chỗ".

Phương thức ẩn danh — là một đoạn mã tạm thời không cần tên vì nó được dùng ngay nơi nó được tạo. Nó:

  • Được khai báo bên trong một phương thức khác.
  • Không có tên.
  • Thường được dùng một lần — ví dụ truyền vào delegate hoặc đăng ký cho sự kiện.

Hiện nay thường dùng lambda- biểu thức (toán tử =>), nhưng hiểu phương thức ẩn danh giúp nắm rõ cách delegate và model sự kiện trong C# hoạt động.

Khi nào phương thức ẩn danh tiện?

  • Cần nhanh chóng truyền một đoạn logic vào tham số, ví dụ cho sắp xếp, lọc, sự kiện v.v.
  • Không đáng để tạo nhiều phương thức riêng trong lớp chỉ vì logic dùng một lần.
  • Khi muốn code gọn và dễ đọc: toàn bộ "logic nhỏ" ở một chỗ.

Một chút lịch sử

Lần đầu trong C# khả năng tạo phương thức ẩn danh xuất hiện ở phiên bản 2.0. Trước đó dùng delegate khá cồng kềnh: phải khai báo phương thức có tên riêng, dù logic chỉ dùng một chỗ. Với phương thức ẩn danh mọi thứ đơn giản hơn, và khi lambda- biểu thức xuất hiện ý tưởng này trở nên ngắn gọn hơn nữa.

2. Kinh điển: delegate và phương thức "có tên" thông thường

Trước khi làm quen với phương thức ẩn danh, hãy nhắc lại cách chuẩn để truyền hành vi qua delegate. Giả sử chúng ta có ứng dụng "Cơ sở dữ liệu sách", nơi muốn lọc sách theo các điều kiện khác nhau.

// Định nghĩa delegate
public delegate bool BookFilter(Book book);

// Lớp Book
public class Book
{
    public string Title { get; set; }
    public int Year { get; set; }
}

Trước đây ta thường viết như sau:

// Hàm lọc riêng
public static bool IsClassic(Book b)
{
    return b.Year < 1970;
}

// Ở đâu đó trong mã sử dụng
BookFilter filter = IsClassic;

Mỗi lần tạo phương thức riêng — không phải lúc nào cũng tiện. Còn nếu có hàng chục bộ lọc?

3. Phương thức ẩn danh: tối giản, thân với delegate

Phương thức ẩn danh cho phép định nghĩa hàm lọc ngay tại nơi cần:

BookFilter filter = delegate(Book b)
{
    return b.Year < 1970;
};

Vậy là xong! Không cần phương thức thừa, mọi thứ tại chỗ. delegate đóng vai khai báo hàm vô danh.

Cú pháp chung

delegate([tham_số])
{
    // thân hàm
};

Ví dụ nhúng vào chương trình:

public class Book
{
    public string Title { get; set; }
    public int Year { get; set; }
}

public delegate bool BookFilter(Book book);

class Program
{
    static void Main()
    {
        Book[] books = {
            new Book { Title = "Master i Margarita", Year = 1967 },
            new Book { Title = "Clean Code", Year = 2008 }
        };

        // Bộ lọc ẩn danh cho sách cổ điển
        BookFilter filter = delegate(Book b)
        {
            return b.Year < 1970;
        };

        foreach (Book book in books)
        {
            if (filter(book)) // gọi hàm ẩn danh!
                Console.WriteLine($"{book.Title} — kinh điển!");
        }
    }
}

Kết quả:

Master i Margarita — kinh điển!

4. Phương thức ẩn danh với các delegate khác nhau

Phương thức ẩn danh hoạt động tốt với mọi delegate. Ví dụ, các Action và Func<T, TResult> chuẩn, mà chúng ta sẽ đi sâu sau này.

Action<string> sayHello = delegate(string name)
{
    Console.WriteLine($"Xin chào, {name}!");
};

sayHello("Thế giới"); // Xin chào, Thế giới!

Minh họa ngắn: cách hoạt động của phương thức ẩn danh "dùng một lần"

Dễ hình dung phương thức ẩn danh như một công nhân tạm thời: đến làm một nhiệm vụ ngắn, xong thì biến mất. Ta liên kết công nhân và nhiệm vụ qua delegate.

Func<int, int, int> sum = delegate (int a, int b) {
    return a + b;
};
Console.WriteLine(sum(5, 7)); // 12

Ứng dụng: sắp xếp, tìm kiếm, xử lý collection

Giả sử có danh sách sách và muốn sắp theo năm xuất bản. Tạo phương thức riêng chỉ để so sánh là thừa.

var books = new List<Book>
{
    new Book { Title = "Master i Margarita", Year = 1967 },
    new Book { Title = "Clean Code", Year = 2008 }
};

books.Sort(delegate(Book a, Book b)
{
    return a.Year.CompareTo(b.Year);
});

foreach (var book in books)
    Console.WriteLine($"{book.Title} ({book.Year})");

5. Những chi tiết hữu ích

Phương thức ẩn danh có giống lambda- biểu thức không?

Trong C# hiện đại thường dùng lambda- biểu thức (=>), và thường thì ít khác biệt, nhưng có vài điểm khác biệt quan trọng:

  • Lambda ngắn gọn và biểu đạt hơn.
  • Lambda có quy tắc bắt giữ biến rõ ràng hơn (chúng ta sẽ học sau).
  • Phương thức ẩn danh xuất hiện sớm hơn và đôi khi gặp trong code "thừa kế" từ dự án cũ.

Ví dụ của ta nếu viết bằng lambda sẽ là:

BookFilter filter = b => b.Year < 1970;

Tuy nhiên biết cả cú pháp cũ và mới đều hữu ích cho phỏng vấn, đọc code người khác và hiểu sâu về delegate.

Phương thức ẩn danh có tham số và không có tham số

Nếu delegate không nhận gì, có thể viết trên một dòng:

Action printHello = delegate { Console.WriteLine("Xin chào!"); };
printHello(); // Xin chào!

Nếu có tham số — vẫn có thể viết trong một dòng:

Action<int> printSquare = delegate (int x) { Console.WriteLine(x * x); };
printSquare(6); // 36

Phương thức ẩn danh và Lambda- biểu thức

Phương thức ẩn danh Lambda- biểu thức
Cú pháp
delegate (...) {}
(args) => {}
Bắt giữ biến Có Có
Độ phổ biến Ít dùng Chuẩn
Trả về giá trị Có thể/phải Có thể/phải
Nhiều dòng Có thể Có thể

6. Phương thức ẩn danh — chi tiết nhỏ và lưu ý sử dụng

Bắt giữ biến cục bộ
Phương thức ẩn danh có thể dùng biến từ phương thức chứa — cơ chế này gọi là "closure". Ví dụ:

int minYear = 1970;

BookFilter filter = delegate(Book book)
{
    return book.Year < minYear;
};

Console.WriteLine(filter(new Book { Title = "Test", Year = 1960 })); // True

Nếu sau đó thay đổi minYear, bộ lọc sẽ dùng giá trị mới!

Có thể không chỉ định tham số
Nếu không cần tham số:

Action sayHi = delegate { Console.WriteLine("Hi!"); };

Truyền null
Nếu delegate không được gán phương thức (ví dụ không có phương thức ẩn danh), giá trị của nó là null, và cố gắng gọi sẽ gây ra NullReferenceException. Cẩn thận.

Nhiều dòng
Phương thức ẩn danh có thể chứa khối mã đầy đủ, điều kiện, vòng lặp và thậm chí các lời gọi khác:

Action manyThings = delegate
{
    Console.WriteLine("Bắt đầu!");
    for (int i = 0; i < 3; i++)
        Console.WriteLine(i);
    Console.WriteLine("Kết thúc!");
};
manyThings();

7. Lỗi thường gặp và đặc điểm

Đôi khi lập trình viên nhầm phạm vi biến: bắt giữ giá trị từ vòng lặp hoặc phương thức lồng nhau có thể dẫn tới kết quả bất ngờ.

List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
    actions.Add(delegate { Console.WriteLine(i); });
}
foreach (var action in actions) action(); // 3 3 3 — bất ngờ!

Vấn đề là phương thức ẩn danh "nhìn thấy" biến i — khi vòng lặp kết thúc, i trở thành 3. Tất cả hàm in cùng một giá trị. Để có hành vi đúng, tốt hơn là bắt giữ biến như sau:

for (int i = 0; i < 3; i++)
{
    int current = i;
    actions.Add(delegate { Console.WriteLine(current); });
}
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Thực hành đầu tiên với phương thức ẩn danh
Thực hành đầu tiên với phương thức ẩn danh
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION