一、委托与事件:C#中的通信基石
在C#中,委托和事件就像现实生活中的"中介"和"通知栏"。委托定义了方法的签名,而事件则基于委托实现了一种发布-订阅模式。我们先看一个简单的委托示例:
// 定义一个委托类型
public delegate void MessageHandler(string message);
class Program
{
// 使用委托作为参数
static void ProcessMessage(MessageHandler handler)
{
handler("Hello from delegate!");
}
static void Main()
{
// 实例化委托并绑定方法
MessageHandler handler = ShowMessage;
ProcessMessage(handler);
}
// 符合委托签名的方法
static void ShowMessage(string msg)
{
Console.WriteLine($"收到消息: {msg}");
}
}
这个例子展示了委托的核心特点:类型安全的方法引用。但实际开发中,我们更多使用事件来避免直接暴露委托。
二、事件的内存陷阱:订阅者的生命周期
事件虽然方便,但如果不注意就会导致内存泄漏。最常见的问题是:当事件发布者比订阅者生命周期更长时,订阅者无法被垃圾回收。看这个典型场景:
public class EventPublisher
{
public event EventHandler SomethingHappened;
public void DoSomething()
{
SomethingHappened?.Invoke(this, EventArgs.Empty);
}
}
public class EventSubscriber
{
public EventSubscriber(EventPublisher publisher)
{
// 订阅事件(潜在的内存泄漏点)
publisher.SomethingHappened += OnSomethingHappened;
}
private void OnSomethingHappened(object sender, EventArgs e)
{
Console.WriteLine("事件被触发");
}
}
class Program
{
static void Main()
{
var publisher = new EventPublisher();
var subscriber = new EventSubscriber(publisher);
// 即使subscriber不再使用,由于publisher仍持有引用,它不会被GC回收
publisher.DoSomething();
}
}
这里的关键问题是:事件的+=操作会创建一个强引用。除非显式使用-=,否则订阅者会一直被发布者"拽着"不放。
三、解决方案:四种正确使用模式
1. 显式取消订阅模式
public class SafeSubscriber : IDisposable
{
private EventPublisher _publisher;
public SafeSubscriber(EventPublisher publisher)
{
_publisher = publisher;
_publisher.SomethingHappened += OnSomethingHappened;
}
private void OnSomethingHappened(object sender, EventArgs e) { /*...*/ }
public void Dispose()
{
// 关键:在不再需要时取消订阅
_publisher.SomethingHappened -= OnSomethingHappened;
}
}
2. 弱事件模式(使用WeakReference)
public class WeakEventManager
{
private WeakReference<EventHandler> _weakHandler;
public void AddHandler(EventHandler handler)
{
_weakHandler = new WeakReference<EventHandler>(handler);
}
public void RaiseEvent()
{
if (_weakHandler.TryGetTarget(out var handler))
{
handler(this, EventArgs.Empty);
}
}
}
3. 使用框架提供的WeakEventManager
.NET提供了现成的解决方案:
public class MyEventSource
{
public static readonly WeakEventManager WeakEventManager = new();
public event EventHandler MyEvent
{
add => WeakEventManager.AddHandler(value);
remove => WeakEventManager.RemoveHandler(value);
}
}
4. 基于接口的订阅模式
public interface IEventSubscriber
{
void HandleEvent();
}
public class EventBus
{
private readonly List<WeakReference<IEventSubscriber>> _subscribers = new();
public void Subscribe(IEventSubscriber subscriber)
{
_subscribers.Add(new WeakReference<IEventSubscriber>(subscriber));
}
public void Publish()
{
foreach (var weakRef in _subscribers.ToList())
{
if (weakRef.TryGetTarget(out var subscriber))
{
subscriber.HandleEvent();
}
else
{
_subscribers.Remove(weakRef); // 自动清理
}
}
}
}
四、实战建议与最佳实践
对象生命周期管理:
- 确保订阅者的生命周期不超过发布者
- 对于长期存在的发布者,优先考虑弱引用模式
清理策略:
// 在适当的位置(如Dispose、析构函数中)取消订阅 public void Dispose() { _publisher.Event -= HandlerMethod; }性能考量:
- 弱引用模式会增加少量性能开销
- 高频事件建议使用强引用+显式管理
调试技巧:
// 检查事件订阅者数量 var count = myEvent?.GetInvocationList()?.Length ?? 0; Console.WriteLine($"当前事件订阅数: {count}");框架选择:
- WPF应用优先使用WeakEventManager
- 纯.NET环境可以考虑自定义弱引用实现
- 跨组件通信推荐使用事件聚合器模式
记住,正确管理事件订阅关系不仅能避免内存泄漏,还能使程序结构更清晰。下次当你使用+=操作符时,不妨先想想:"这个订阅关系将来要怎么解除?"
评论