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 = "测试", 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); });
}
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION