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 使用。比如內建的 Action 與 Func<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 表達式 | |
|---|---|---|
| 語法 | |
|
| 變數捕獲 | 是 | 是 |
| 普及度 | 較少使用 | 常用 |
| 返回值 | 可 / 必要 | 可 / 必要 |
| 多行支援 | 可以 | 可以 |
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); });
}
GO TO FULL VERSION