Skip to content

C# - 委托

C# 中的**委托(delegate)**是一种安全封装方法的类型,类似于 C++ 中的函数指针。它充当对具有特定参数列表和返回类型的方法的引用。委托是事件、回调和 LINQ 的基础。

虽然您可以声明自定义委托类型,但现代 C# 开发主要依赖内置的泛型委托(Func、Action、Predicate)和Lambda 表达式,以实现更简洁和强大的语法。

.NET 库提供了内置的泛型委托类型,涵盖了大多数常见场景,从而无需声明自定义委托。

委托描述示例
Func<T, TResult>表示一个接受一个或多个输入参数(T)并返回一个值(TResult)的方法。有最多支持 16 个输入参数的重载。Func<int, int, int> add; // 接收两个 int,返回一个 int。
Action<T>表示一个接受一个或多个输入参数(T)但不返回任何值(void)的方法。Action<string> print; // 接收一个 string,不返回任何值(void)。
Predicate<T>Func 的一个特化版本,它接受一个参数(T)并始终返回一个 bool 值。它通常用于测试条件。Predicate<int> isEven; // 接收一个 int,返回一个 bool。

一个**Lambda 表达式(lambda expression)**是一个匿名函数,可用于创建委托或表达式树类型。它提供了一种编写方法的简写方式,而无需完整的 方法声明。

// Lambda 语法:(输入参数) => { 表达式或语句块 }
// 示例 1:一个 Func<>,用于添加两个数字
Func<int, int, int> add = (x, y) => x + y;
int sum = add(10, 5);
Console.WriteLine($"Sum: {sum}"); // 输出:Sum: 15
// 示例 2:一个 Action<>,用于打印消息
Action<string> greet = name => Console.WriteLine($"Hello, {name}!");
greet("World"); // 输出:Hello, World!
// 示例 3:一个 Predicate<>,用于检查数字是否为正数
Predicate<int> isPositive = n => n > 0;
Console.WriteLine($"Is -5 positive? {isPositive(-5)}"); // 输出:Is -5 positive? False

委托有一个强大的特性,称为多播(multicasting),即单个委托可以引用并调用多个方法。您可以使用 + 和 -(或 += 和 -=)运算符向委托的调用列表添加或移除方法。

Action<string> logger;
Action<string> logToConsole = message => Console.WriteLine($"CONSOLE: {message}");
Action<string> logToFile = message => System.IO.File.AppendAllText("log.txt", $"{message}\n");
// 将两个方法组合成一个委托实例
logger = logToConsole;
logger += logToFile;
// 调用 'logger' 现在将同时调用两个方法
logger("System startup complete.");
// 移除一个方法
logger -= logToConsole;
logger("User logged in."); // 这将只写入到文件中
Console.WriteLine("Logging complete. Check log.txt");

当您运行此代码时,“System startup complete.”会同时打印到控制台和日志文件。“User logged in.”则只写入到日志文件。

委托是 LINQ 的支柱。像 Where、Select 和 OrderBy 这样的方法都接受委托(通常是 Lambda 表达式)来执行它们的操作。

var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// 'Where' 接收 Func<int, bool>(或 Predicate<int>)
var evenNumbers = numbers.Where(n => n % 2 == 0);
// 'Select' 接收 Func<int, TResult>
var squaredNumbers = evenNumbers.Select(n => n * n);
Console.WriteLine(string.Join(", ", squaredNumbers)); // 输出:4, 16, 36, 64, 100

C# 中的事件使用委托来定义事件处理程序的签名。当事件触发时,所有注册的处理方法都会被调用。

public class Button
{
// 事件基于 Action 委托
public event Action? Clicked;
public void SimulateClick()
{
Console.WriteLine("Button was clicked!");
Clicked?.Invoke(); // 安全地调用委托
}
}
var myButton = new Button();
// 使用 Lambda 表达式订阅事件
myButton.Clicked += () => Console.WriteLine("Event handler: Responding to click!");
myButton.Clicked += () => Console.WriteLine("Another handler: Updating UI...");
myButton.SimulateClick();

当上述代码被编译和执行时,会产生以下结果:

Button was clicked!
Event handler: Responding to click!
Another handler: Updating UI...