Skip to main content

委托与事件

什么是委托?

核心概念

委托是 C# 中的一种类型安全的函数指针,可以引用一个或多个具有相同签名的方法。

委托基础

声明和使用委托

// 声明委托类型
public delegate int MathOperation(int a, int b);

public class Calculator
{
// 匹配委托签名的方法
public static int Add(int a, int b) => a + b;
public static int Subtract(int a, int b) => a - b;
public static int Multiply(int a, int b) => a * b;
}

// 使用委托
class Program
{
static void Main()
{
// 创建委托实例
MathOperation operation = Calculator.Add;

// 调用委托
int result = operation(10, 5); // 15
Console.WriteLine($"结果: {result}");

// 改变委托引用的方法
operation = Calculator.Multiply;
result = operation(10, 5); // 50
Console.WriteLine($"结果: {result}");
}
}

多播委托

public delegate void LogHandler(string message);

public class Logger
{
public static void LogToConsole(string message)
{
Console.WriteLine($"[Console] {message}");
}

public static void LogToFile(string message)
{
File.AppendAllText("log.txt", $"[File] {message}\n");
}
}

// 使用多播委托
LogHandler logger = Logger.LogToConsole;
logger += Logger.LogToFile; // 添加多个方法

// 调用委托,会依次执行所有方法
logger("这是一条日志");
// 输出:
// [Console] 这是一条日志
// 并写入文件

// 移除方法
logger -= Logger.LogToFile;

内置委托类型

委托类型说明签名使用场景
Action无返回值void Method()执行操作
Action<T>无返回值,带参数void Method(T arg)执行操作(带参数)
Func<TResult>有返回值TResult Method()计算并返回结果
Func<T, TResult>有返回值,带参数TResult Method(T arg)转换数据
Predicate<T>返回 boolbool Method(T arg)条件判断

Action 委托

// Action: 无参数,无返回值
Action greet = () => Console.WriteLine("Hello!");
greet();

// Action<T>: 有参数,无返回值
Action<string> printMessage = message => Console.WriteLine(message);
printMessage("Hello World");

// Action<T1, T2>: 多个参数
Action<string, int> printInfo = (name, age) =>
Console.WriteLine($"{name} is {age} years old");
printInfo("张三", 25);

Func 委托

// Func<TResult>: 无参数,有返回值
Func<int> getNumber = () => 42;
int number = getNumber();

// Func<T, TResult>: 有参数,有返回值
Func<int, int> square = x => x * x;
int result = square(5); // 25

// Func<T1, T2, TResult>: 多个参数
Func<int, int, int> add = (a, b) => a + b;
int sum = add(10, 20); // 30

Predicate 委托

// Predicate<T>: 判断条件
Predicate<int> isEven = x => x % 2 == 0;
bool result = isEven(4); // true

// 用于集合筛选
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> evenNumbers = numbers.FindAll(isEven);
// 结果: [2, 4, 6]

Lambda 表达式

Lambda 表达式

Lambda 表达式是创建委托实例的简洁语法。

// 传统方式:定义方法
public int Add(int a, int b)
{
return a + b;
}

// 创建委托
Func<int, int, int> operation = Add;
int result = operation(5, 3);

Lambda 表达式语法

// 无参数
Action action = () => Console.WriteLine("Hello");

// 单个参数(可以省略括号)
Func<int, int> square = x => x * x;

// 多个参数
Func<int, int, int> add = (a, b) => a + b;

// 语句块
Func<int, string> format = x =>
{
if (x < 0) return "负数";
if (x == 0) return "零";
return "正数";
};

// 显式类型
Func<int, int, int> multiply = (int a, int b) => a * b;

事件 (Events)

事件是什么?

事件是一种特殊的委托,用于实现发布-订阅模式,允许对象在状态改变时通知其他对象。

声明和使用事件

// 1. 定义事件参数
public class OrderEventArgs : EventArgs
{
public string OrderId { get; set; }
public decimal Amount { get; set; }
}

// 2. 定义发布者
public class OrderService
{
// 声明事件(使用 EventHandler<T>)
public event EventHandler<OrderEventArgs> OrderCreated;

// 触发事件的方法
protected virtual void OnOrderCreated(OrderEventArgs e)
{
OrderCreated?.Invoke(this, e);
}

public void CreateOrder(string orderId, decimal amount)
{
// 业务逻辑
Console.WriteLine($"创建订单: {orderId}");

// 触发事件
OnOrderCreated(new OrderEventArgs
{
OrderId = orderId,
Amount = amount
});
}
}

// 3. 定义订阅者
public class EmailService
{
public void OnOrderCreated(object sender, OrderEventArgs e)
{
Console.WriteLine($"发送邮件: 订单 {e.OrderId} 已创建");
}
}

public class SmsService
{
public void OnOrderCreated(object sender, OrderEventArgs e)
{
Console.WriteLine($"发送短信: 订单 {e.OrderId} 金额 {e.Amount}");
}
}

// 4. 使用
var orderService = new OrderService();
var emailService = new EmailService();
var smsService = new SmsService();

// 订阅事件
orderService.OrderCreated += emailService.OnOrderCreated;
orderService.OrderCreated += smsService.OnOrderCreated;

// 触发事件
orderService.CreateOrder("ORD001", 299.99m);

// 输出:
// 创建订单: ORD001
// 发送邮件: 订单 ORD001 已创建
// 发送短信: 订单 ORD001 金额 299.99

委托 vs 事件

特性委托事件
外部调用✅ 可以在类外部调用❌ 只能在类内部触发
外部赋值✅ 可以被外部直接赋值❌ 只能 += 和 -=
使用场景回调函数、策略模式发布-订阅模式
安全性较低较高
public class Demo
{
// 委托字段
public Action OnAction;

// 事件
public event Action OnEvent;

public void Test()
{
// ✅ 类内部可以调用
OnAction?.Invoke();
OnEvent?.Invoke();
}
}

// 外部使用
var demo = new Demo();

// ✅ 委托可以被外部直接赋值和调用
demo.OnAction = () => Console.WriteLine("Action");
demo.OnAction();

// ❌ 事件不能被外部赋值和调用(编译错误)
// demo.OnEvent = () => Console.WriteLine("Event"); // 错误
// demo.OnEvent(); // 错误

// ✅ 事件只能订阅和取消订阅
demo.OnEvent += () => Console.WriteLine("Event");
demo.OnEvent -= () => Console.WriteLine("Event");

实战案例

案例1:进度报告

public class FileDownloader
{
// 定义进度事件
public event EventHandler<int> ProgressChanged;

public async Task DownloadAsync(string url, string filePath)
{
using var client = new HttpClient();
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);

var totalBytes = response.Content.Headers.ContentLength ?? 0;
var buffer = new byte[8192];
var totalRead = 0L;

using var stream = await response.Content.ReadAsStreamAsync();
using var fileStream = File.Create(filePath);

int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await fileStream.WriteAsync(buffer, 0, bytesRead);
totalRead += bytesRead;

// 报告进度
var progress = (int)((totalRead * 100) / totalBytes);
ProgressChanged?.Invoke(this, progress);
}
}
}

// 使用
var downloader = new FileDownloader();

// 订阅进度事件
downloader.ProgressChanged += (sender, progress) =>
{
Console.WriteLine($"下载进度: {progress}%");
};

await downloader.DownloadAsync("https://example.com/file.zip", "file.zip");

案例2:命令模式

public class Command
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;

public Command(Action execute, Func<bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}

public event EventHandler CanExecuteChanged;

public bool CanExecute() => _canExecute?.Invoke() ?? true;

public void Execute()
{
if (CanExecute())
{
_execute();
}
}

public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}

// 使用
bool canSave = false;

var saveCommand = new Command(
execute: () => Console.WriteLine("保存成功"),
canExecute: () => canSave
);

saveCommand.Execute(); // 不执行,因为 canSave = false

canSave = true;
saveCommand.RaiseCanExecuteChanged();
saveCommand.Execute(); // 保存成功

案例3:观察者模式

public class Stock
{
private decimal _price;

public string Symbol { get; set; }

public decimal Price
{
get => _price;
set
{
if (_price != value)
{
_price = value;
PriceChanged?.Invoke(this, _price);
}
}
}

public event EventHandler<decimal> PriceChanged;
}

public class StockAlert
{
private readonly decimal _threshold;

public StockAlert(decimal threshold)
{
_threshold = threshold;
}

public void Subscribe(Stock stock)
{
stock.PriceChanged += OnPriceChanged;
}

private void OnPriceChanged(object sender, decimal price)
{
var stock = (Stock)sender;
if (price >= _threshold)
{
Console.WriteLine($"警告: {stock.Symbol} 价格达到 {price:C}");
}
}
}

// 使用
var stock = new Stock { Symbol = "AAPL", Price = 150 };
var alert = new StockAlert(threshold: 160);

alert.Subscribe(stock);

stock.Price = 155; // 无输出
stock.Price = 165; // 输出: 警告: AAPL 价格达到 $165.00

案例4:UI 事件处理

public class Button
{
public event EventHandler Clicked;

public void Click()
{
Clicked?.Invoke(this, EventArgs.Empty);
}
}

// 使用
var button = new Button();

// 订阅多个事件处理器
button.Clicked += (sender, e) => Console.WriteLine("处理器1: 按钮被点击");
button.Clicked += (sender, e) => Console.WriteLine("处理器2: 记录日志");
button.Clicked += (sender, e) => Console.WriteLine("处理器3: 发送分析数据");

button.Click();
// 输出:
// 处理器1: 按钮被点击
// 处理器2: 记录日志
// 处理器3: 发送分析数据

最佳实践

委托与事件最佳实践

1. 事件命名约定

// ✅ 好:使用过去式
public event EventHandler Clicked;
public event EventHandler OrderCreated;

// ❌ 差:使用现在进行时
public event EventHandler Clicking;
public event EventHandler CreatingOrder;

2. 使用 EventHandler<T>

// ✅ 好:使用标准的 EventHandler<T>
public event EventHandler<OrderEventArgs> OrderCreated;

// ❌ 差:自定义委托
public delegate void OrderCreatedHandler(OrderEventArgs e);
public event OrderCreatedHandler OrderCreated;

3. 检查 null

// ✅ 好:使用 null 条件运算符
OrderCreated?.Invoke(this, e);

// ✅ 也可以:先复制到局部变量
var handler = OrderCreated;
handler?.Invoke(this, e);

// ❌ 差:不检查 null
OrderCreated.Invoke(this, e); // 可能 NullReferenceException

4. 事件参数继承 EventArgs

// ✅ 好:继承 EventArgs
public class OrderEventArgs : EventArgs
{
public string OrderId { get; set; }
}

// ❌ 差:不继承 EventArgs
public class OrderData
{
public string OrderId { get; set; }
}

5. 防止内存泄漏

public class Subscriber : IDisposable
{
private readonly Publisher _publisher;

public Subscriber(Publisher publisher)
{
_publisher = publisher;
_publisher.SomeEvent += OnSomeEvent;
}

private void OnSomeEvent(object sender, EventArgs e)
{
// 处理事件
}

public void Dispose()
{
// 取消订阅,防止内存泄漏
_publisher.SomeEvent -= OnSomeEvent;
}
}

6. 异步事件处理

public class AsyncEventPublisher
{
// 使用 Func 而不是 Action
public event Func<object, EventArgs, Task> AsyncEvent;

protected virtual async Task OnAsyncEvent(EventArgs e)
{
var handler = AsyncEvent;
if (handler != null)
{
// 并行执行所有处理器
var delegates = handler.GetInvocationList();
var tasks = delegates
.Cast<Func<object, EventArgs, Task>>()
.Select(d => d(this, e));

await Task.WhenAll(tasks);
}
}
}

总结

关键要点
  • 委托是类型安全的函数指针
  • 使用 ActionFuncPredicate 等内置委托类型
  • Lambda 表达式是创建委托的简洁方式
  • 事件用于实现发布-订阅模式
  • 事件比委托更安全,只能在类内部触发
  • 记得取消事件订阅以防止内存泄漏

相关资源