1. 从现实需求理解委托与事件
在Windows窗体应用中,我们经常需要处理按钮点击操作。假设你正在开发一个订单管理系统,当用户点击"提交订单"按钮时,需要执行以下操作:
- 验证表单数据
- 计算订单总价
- 保存到数据库
- 发送邮件通知
传统做法可能会这样写:
csharp复制private void btnSubmit_Click(object sender, EventArgs e)
{
ValidateForm();
CalculateTotal();
SaveToDatabase();
SendNotification();
}
这种硬编码方式存在明显问题:如果后续需要添加新的处理逻辑(比如记录操作日志),就必须修改btnSubmit_Click方法。这违反了开闭原则(对扩展开放,对修改关闭)。
委托(delegate)正是为了解决这类问题而生的。它本质上是一种类型安全的函数指针,允许我们将方法作为参数传递。在C#中,委托的声明方式如下:
csharp复制public delegate void OrderSubmitHandler(Order order);
这个委托可以指向任何接受Order参数且无返回值的方法。通过委托,我们可以将上述点击事件重构为:
csharp复制private OrderSubmitHandler submitHandlers;
public Form1()
{
InitializeComponent();
submitHandlers += ValidateForm;
submitHandlers += CalculateTotal;
submitHandlers += SaveToDatabase;
submitHandlers += SendNotification;
}
private void btnSubmit_Click(object sender, EventArgs e)
{
submitHandlers?.Invoke(currentOrder);
}
注意:委托实例使用前必须检查是否为null(?.操作符),否则可能引发NullReferenceException
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 委托的底层机制与高级用法
2.1 委托的编译原理
当编译器遇到delegate关键字时,会生成一个继承自System.MulticastDelegate的类。以上面的OrderSubmitHandler为例,编译器会生成包含以下关键成员的类:
- Invoke:同步调用委托链中的所有方法
- BeginInvoke/EndInvoke:异步调用模式(.NET Core后已移除)
- GetInvocationList:获取委托链中的方法列表
委托的一个重要特性是支持多播(multicast),即一个委托实例可以包含多个方法。当调用多播委托时,这些方法会按照添加顺序依次执行。
2.2 内置泛型委托
.NET框架提供了几个常用的泛型委托,避免重复定义:
-
Action:无返回值的方法
- Action:无参数
- Action
:1个参数 - Action<T1,T2>:2个参数
- ...最多16个参数
-
Func:有返回值的方法
- Func
:无参数,返回TResult - Func<T,TResult>:1个参数
- ...最多16个输入参数
- Func
-
Predicate
:返回bool的单个参数方法
使用这些内置委托可以简化代码:
csharp复制// 代替自定义的OrderSubmitHandler
Action<Order> submitHandlers;
2.3 匿名方法与Lambda表达式
C# 2.0引入了匿名方法,3.0引入了Lambda表达式,使委托使用更加简洁:
csharp复制// 匿名方法
submitHandlers += delegate(Order order) {
Console.WriteLine($"Processing order {order.Id}");
};
// Lambda表达式
submitHandlers += order => Console.WriteLine($"Processing order {order.Id}");
Lambda表达式在LINQ查询中尤其有用:
csharp复制var highValueOrders = orders.Where(o => o.Total > 1000)
.OrderBy(o => o.Date);
3. 事件:封装后的委托
3.1 事件的基本语法
事件是对委托的封装,提供了更安全的发布-订阅模型。声明事件的完整语法:
csharp复制public class OrderService
{
// 1. 定义委托类型
public delegate void OrderProcessedHandler(Order order);
// 2. 声明事件
public event OrderProcessedHandler OrderProcessed;
// 3. 触发事件的方法
protected virtual void OnOrderProcessed(Order order)
{
OrderProcessed?.Invoke(order);
}
public void ProcessOrder(Order order)
{
// 处理逻辑...
OnOrderProcessed(order);
}
}
外部类可以订阅这个事件:
csharp复制var service = new OrderService();
service.OrderProcessed += SendNotification;
3.2 事件与委托的关键区别
-
封装性:
- 委托字段可以直接被外部类调用(service.submitHandlers.Invoke())
- 事件只能由声明它的类触发(外部类只能+=/-=)
-
线程安全:
- 事件在触发时会创建委托链的副本,避免多线程下的竞态条件
- 原始委托在多线程环境下需要手动同步
-
设计意图:
- 委托强调回调机制
- 事件强调通知机制
3.3 标准事件模式
.NET框架定义了事件声明的标准模式:
csharp复制public class EventArgs<T> : EventArgs
{
public T Data { get; }
public EventArgs(T data)
{
Data = data;
}
}
public class OrderService
{
public event EventHandler<EventArgs<Order>> OrderProcessed;
protected virtual void OnOrderProcessed(Order order)
{
OrderProcessed?.Invoke(this, new EventArgs<Order>(order));
}
}
这种模式的优势:
- 统一了事件签名(object sender, EventArgs e)
- 支持事件数据的强类型传递
- 与WinForms/WPF事件模型兼容
4. 实战中的常见问题与解决方案
4.1 内存泄漏问题
事件订阅可能导致内存泄漏:
csharp复制var service = new OrderService();
var logger = new OrderLogger();
service.OrderProcessed += logger.LogOrder; // logger现在被service引用
即使外部不再引用logger,由于service仍持有它的引用,GC无法回收logger。解决方法:
- 实现IDisposable:
csharp复制public class OrderLogger : IDisposable
{
private OrderService _service;
public OrderLogger(OrderService service)
{
_service = service;
_service.OrderProcessed += LogOrder;
}
public void Dispose()
{
_service.OrderProcessed -= LogOrder;
}
private void LogOrder(object sender, EventArgs e) { ... }
}
- 使用弱事件模式(WPF中的WeakEventManager)
4.2 事件顺序控制
当多个订阅者注册到同一事件时,执行顺序可能与预期不符。解决方案:
- 显式控制调用顺序:
csharp复制protected virtual void OnOrderProcessed(Order order)
{
var handlers = OrderProcessed?.GetInvocationList();
if (handlers != null)
{
foreach (var handler in handlers.OrderBy(h => h.Target.GetType().Name))
{
handler.DynamicInvoke(this, EventArgs.Empty);
}
}
}
- 使用优先级队列模式:
csharp复制public class OrderedEventHandler
{
private SortedList<int, EventHandler> _handlers = new SortedList<int, EventHandler>();
public void AddHandler(int priority, EventHandler handler)
{
_handlers.Add(priority, handler);
}
public void Invoke(object sender, EventArgs e)
{
foreach (var handler in _handlers.Values)
{
handler(sender, e);
}
}
}
4.3 异步事件处理
传统事件模型是同步的,长时间运行的事件处理会阻塞发布者。异步处理方案:
- 异步事件模式:
csharp复制public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e);
public event AsyncEventHandler<OrderEventArgs> AsyncOrderProcessed;
protected virtual async Task OnAsyncOrderProcessed(OrderEventArgs e)
{
var handlers = AsyncOrderProcessed?.GetInvocationList();
if (handlers != null)
{
foreach (var handler in handlers)
{
await ((AsyncEventHandler<OrderEventArgs>)handler)(this, e);
}
}
}
- 使用Channel实现生产者-消费者模式:
csharp复制public class OrderEventChannel
{
private Channel<Order> _channel = Channel.CreateUnbounded<Order>();
public async Task ProcessOrdersAsync(CancellationToken token)
{
await foreach (var order in _channel.Reader.ReadAllAsync(token))
{
try
{
await ProcessOrderAsync(order);
}
catch (Exception ex)
{
LogError(ex);
}
}
}
public void PostOrder(Order order)
{
_channel.Writer.TryWrite(order);
}
}
5. 高级应用场景
5.1 跨线程事件处理
在WinForms/WPF中,从非UI线程更新控件会引发异常。解决方案:
csharp复制// WinForms
public event EventHandler<string> StatusUpdated;
protected virtual void OnStatusUpdated(string message)
{
if (InvokeRequired)
{
Invoke(new Action(() => OnStatusUpdated(message)));
return;
}
StatusUpdated?.Invoke(this, message);
}
// WPF
public event EventHandler<string> StatusUpdated;
protected virtual void OnStatusUpdated(string message)
{
Application.Current.Dispatcher.Invoke(() =>
{
StatusUpdated?.Invoke(this, message);
});
}
5.2 基于事件的中间件管道
模仿ASP.NET Core中间件,实现处理管道:
csharp复制public class OrderProcessingPipeline
{
private readonly List<Func<Order, Func<Order, Task>, Task>> _middlewares = new List<Func<Order, Func<Order, Task>, Task>>();
public void Use(Func<Order, Func<Order, Task>, Task> middleware)
{
_middlewares.Add(middleware);
}
public async Task ProcessAsync(Order order)
{
Func<Order, Task> pipeline = _ => Task.CompletedTask;
for (int i = _middlewares.Count - 1; i >= 0; i--)
{
var current = _middlewares[i];
var next = pipeline;
pipeline = order => current(order, next);
}
await pipeline(order);
}
}
使用方式:
csharp复制var pipeline = new OrderProcessingPipeline();
pipeline.Use(async (order, next) => {
Console.WriteLine($"Start processing {order.Id}");
await next(order);
Console.WriteLine($"Finished processing {order.Id}");
});
5.3 基于事件的领域驱动设计
在DDD中,领域事件是重要模式:
csharp复制public interface IDomainEvent { }
public class OrderCreatedEvent : IDomainEvent
{
public Order Order { get; }
public DateTime OccurredOn { get; } = DateTime.UtcNow;
public OrderCreatedEvent(Order order)
{
Order = order;
}
}
public interface IEventHandler<T> where T : IDomainEvent
{
Task HandleAsync(T @event);
}
public class OrderCreatedEventHandler : IEventHandler<OrderCreatedEvent>
{
public async Task HandleAsync(OrderCreatedEvent @event)
{
// 发送通知、更新报表等
}
}
public class EventDispatcher
{
private readonly IServiceProvider _serviceProvider;
public EventDispatcher(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task DispatchAsync(IDomainEvent @event)
{
var handlerType = typeof(IEventHandler<>).MakeGenericType(@event.GetType());
var handlers = _serviceProvider.GetServices(handlerType);
foreach (var handler in handlers)
{
var method = handlerType.GetMethod("HandleAsync");
await (Task)method.Invoke(handler, new object[] { @event });
}
}
}
6. 性能优化与最佳实践
6.1 委托性能考量
- 避免频繁创建委托实例:
csharp复制// 不好:每次调用都创建新委托
for (int i = 0; i < 10000; i++)
{
list.ForEach(x => x.Process());
}
// 更好:缓存委托实例
Action<Item> processAction = x => x.Process();
for (int i = 0; i < 10000; i++)
{
list.ForEach(processAction);
}
- 对于简单方法,MethodGroup转换比Lambda更高效:
csharp复制// 编译器生成优化代码
submitHandlers += ValidateForm;
// 生成额外的匿名类
submitHandlers += order => ValidateForm(order);
6.2 事件设计准则
-
命名规范:
- 事件:使用动词或动词短语(OrderProcessed)
- 委托类型:EventHandler后缀(OrderProcessedEventHandler)
- 触发方法:On前缀(OnOrderProcessed)
-
线程安全模式:
csharp复制private EventHandler<OrderEventArgs> _orderProcessed;
private readonly object _eventLock = new object();
public event EventHandler<OrderEventArgs> OrderProcessed
{
add
{
lock (_eventLock)
{
_orderProcessed += value;
}
}
remove
{
lock (_eventLock)
{
_orderProcessed -= value;
}
}
}
- 为事件提供扩展方法:
csharp复制public static class OrderServiceExtensions
{
public static IDisposable RegisterOrderProcessed(
this OrderService service,
Action<object, OrderEventArgs> handler)
{
service.OrderProcessed += handler;
return new DisposableAction(() => service.OrderProcessed -= handler);
}
}
public class DisposableAction : IDisposable
{
private Action _action;
public DisposableAction(Action action)
{
_action = action;
}
public void Dispose()
{
_action?.Invoke();
_action = null;
}
}
6.3 调试技巧
- 查看委托链内容:
csharp复制var handlers = submitHandlers?.GetInvocationList();
if (handlers != null)
{
foreach (var handler in handlers)
{
Console.WriteLine($"Method: {handler.Method.Name}, Target: {handler.Target?.GetType().Name}");
}
}
- 使用ConditionalWeakTable跟踪订阅者:
csharp复制private readonly ConditionalWeakTable<object, string> _subscribers = new ConditionalWeakTable<object, string>();
public event EventHandler OrderProcessed
{
add
{
_subscribers.Add(value.Target, $"Subscribed at {DateTime.Now}");
OrderProcessedInternal += value;
}
remove
{
OrderProcessedInternal -= value;
_subscribers.Remove(value.Target);
}
}
private event EventHandler OrderProcessedInternal;
- 诊断内存泄漏:
csharp复制// 在事件发布者中添加
~OrderService()
{
Debug.WriteLine($"Finalizing OrderService with {_subscribers.Count} subscribers");
}
