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 = "测试", Year = 1960 })); // True
如果之后修改了 minYear,过滤器会使用新的值!
可以不指定参数
如果不需要参数:
Action sayHi = delegate { Console.WriteLine("嗨!"); };
传递 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