委托与事件
什么是委托?
核心概念
委托是 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> | 返回 bool | bool 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 表达式是创建委托实例的简洁语法。
- 传统写法
- Lambda 表达式
// 传统方式:定义方法
public int Add(int a, int b)
{
return a + b;
}
// 创建委托
Func<int, int, int> operation = Add;
int result = operation(5, 3);
// Lambda 表达式:简洁写法
Func<int, int, int> operation = (a, b) => a + b;
int result = operation(5, 3);
// 多行 Lambda
Func<int, int, int> complexOperation = (a, b) =>
{
int sum = a + b;
int result = sum * 2;
return result;
};
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);
}
}
}
总结
关键要点
- 委托是类型安全的函数指针
- 使用
Action
、Func
、Predicate
等内置委托类型 - Lambda 表达式是创建委托的简洁方式
- 事件用于实现发布-订阅模式
- 事件比委托更安全,只能在类内部触发
- 记得取消事件订阅以防止内存泄漏