CodeGym /課程 /C# SELF /認識匿名方法 ( delegate)

認識匿名方法 ( delegate)

C# SELF
等級 49 , 課堂 0
開放

1. 介紹

當你寫程式時,有時只需要一段很簡單、只會用一次的邏輯。為它額外寫一個具名方法,就像用飛機組裝工具箱裡的鎚子釘一顆釘子一樣誇張。更簡單的做法是就地解決——用手邊的東西完成任務。

匿名方法 是這種暫時性的程式碼片段,因為只在創建它的地方使用,所以不需要名字。它有以下特性:

  • 在另一個方法內部宣告。
  • 沒有名字。
  • 通常單次使用——例如傳給 delegate 或訂閱事件。

現在比較常用的是 lambda 表達式(運算子 =>),但理解匿名方法有助於弄懂 delegate 和 C# 的事件模型。

什麼時候匿名方法比較方便?

  • 需要快速把一小段邏輯傳給參數,例如做排序、過濾、事件處理等時。
  • 沒必要為一次性邏輯在類裡增加一堆方法。
  • 想讓程式碼緊湊、易讀:所有「小的業務邏輯」集中在同一處。

一點歷史

匿名方法在 C# 裡最早出現在版本 2.0。在那之前使用 delegate 很繁瑣:即便某段邏輯只用一次,也得宣告一個具名方法。引入匿名方法之後情況簡化了,後來有了 lambda 表達式,語法就更簡潔了。

2. 經典範例:delegates 和傳統「具名」方法

在了解匿名方法之前,先回顧透過 delegate 傳遞行為的標準做法。假設我們有個「書籍資料庫」應用,想要根據不同條件過濾書籍。

// 定義 delegate
public delegate bool BookFilter(Book book);

// 書籍類別
public class Book
{
    public string Title { get; set; }
    public int Year { get; set; }
}

以前我們會這樣寫:

// 獨立的過濾函式
public static bool IsClassic(Book b)
{
    return b.Year < 1970;
}

// 在某處使用程式碼
BookFilter filter = IsClassic;

每次都搞一個獨立方法並不方便。如果過濾條件有好幾十種呢?

3. 匿名方法:極簡、與 delegate 的好朋友

匿名方法允許你直接在需要的地方定義過濾邏輯:

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

就這樣!不用多出方法,一切都在原地。 delegate 當作無名函式的宣告。

通用語法

delegate([參數])
{
    // 方法主體
};

內嵌到程式裡的範例:

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 = "馬斯特與瑪格麗塔", Year = 1967 },
            new Book { Title = "Clean Code", Year = 2008 }
        };

        // 匿名過濾器:判斷經典書
        BookFilter filter = delegate(Book b)
        {
            return b.Year < 1970;
        };

        foreach (Book book in books)
        {
            if (filter(book)) // 呼叫匿名函式!
                Console.WriteLine($"{book.Title} — 經典!");
        }
    }
}

輸出:

馬斯特與瑪格麗塔 — 經典!

4. 匿名方法與不同 delegate 的搭配

匿名方法可以配合各種 delegate 使用。比如內建的 ActionFunc<T, TResult>,我們之後會詳細介紹它們。

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

sayHello("世界"); // 嗨,世界!

簡短示範:一次性匿名方法是怎麼運作的

把匿名方法想像成臨時工:來執行一個短任務,做完就走。用 delegate 把臨時工和任務綁在一起。

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

應用場景:排序、搜尋、集合處理

假設我們有一本書的清單,想根據出版年排序。為了臨時的比較邏輯去寫個方法就太冗了。

var books = new List<Book>
{
    new Book { Title = "馬斯特與瑪格麗塔", 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. 有用的細節

匿名方法和 lambda 表達式是一樣的嗎?

在現代的 C# 更常用的是 lambda 表達式(運算子 =>),兩者通常差異不大,但還是有些重要區別:

  • lambda 表達式更短、更易表達。
  • lambda 對變數捕獲有比較明確的規則(我們稍後會學)。
  • 匿名方法出現得比較早,有時在舊專案裡會看到它們的「遺留」用法。

如果用 lambda 寫,我們的例子會是:

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

不過了解舊語法跟新語法都很有用,對面試、讀別人的程式碼以及深入理解 delegate 都有幫助。

有參數和無參數的匿名方法

如果 delegate 不帶參數,可以寫成一行:

Action printHello = delegate { Console.WriteLine("嗨!"); };
printHello(); // 嗨!

如果有參數,也可以寫成一行:

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

匿名方法 vs Lambda 表達式

匿名方法 lambda 表達式
語法
delegate (...) {}
(args) => {}
變數捕獲
普及度 較少使用 常用
返回值 可 / 必要 可 / 必要
多行支援 可以 可以

6. 匿名方法的細節與使用注意

捕獲本地變數
匿名方法可以使用外層方法的變數——這個機制叫做「閉包」。例如:

int minYear = 1970;

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

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

如果之後改變了 minYear,過濾器會使用新的值!

可以不指定參數
如果不需要參數:

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

傳遞 null
如果 delegate 沒有被指派方法(例如匿名方法),它的值就是 null,嘗試呼叫會導致 NullReferenceException。小心點。

多行情況
匿名方法可以包含完整的程式區塊、條件、迴圈甚至其他呼叫:

Action manyThings = delegate
{
    Console.WriteLine("開始!");
    for (int i = 0; i < 3; i++)
        Console.WriteLine(i);
    Console.WriteLine("結束!");
};
manyThings();

7. 常見錯誤與特性

有時程式設計師會搞混變數的作用域:從迴圈或內部方法捕獲變數可能導致意外結果。

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 — 驚訝!

問題在於匿名方法會「看到」變數 i — 當迴圈結束時,i 變成了 3。所有方法都印出相同的值。正確的做法是捕獲一個新的變數,如下:

for (int i = 0; i < 3; i++)
{
    int current = i;
    actions.Add(delegate { Console.WriteLine(current); });
}
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION