跳转到内容

C Sharp 弱事件模式

来自代码酷
Admin留言 | 贡献2025年4月29日 (二) 18:40的版本 (Page creation by admin bot)

(差异) ←上一版本 | 已核准修订 (差异) | 最后版本 (差异) | 下一版本→ (差异)


C#弱事件模式是一种特殊的事件处理机制,用于解决标准事件订阅可能导致的内存泄漏问题。本指南将详细介绍其原理、实现方式及实际应用场景。

概述[编辑 | 编辑源代码]

在标准事件模型中,事件发布者持有对订阅者的强引用,可能导致订阅者无法被垃圾回收(即使不再需要)。弱事件模式通过使用弱引用WeakReference)来打破这种强依赖关系。

核心问题[编辑 | 编辑源代码]

传统事件订阅的潜在问题:

public class Publisher
{
    public event EventHandler? EventRaised;
}

public class Subscriber
{
    public Subscriber(Publisher pub) 
    {
        pub.EventRaised += HandleEvent; // 强引用!
    }

    private void HandleEvent(object? sender, EventArgs e) { }
}

PublisherSubscriber生命周期更长,即使Subscriber不再使用,仍会因事件绑定而保留在内存中。

实现方式[编辑 | 编辑源代码]

.NET 提供两种主要实现方案:

1. WeakEventManager 类[编辑 | 编辑源代码]

位于System.Windows命名空间(WPF基础库):

public class CustomWeakEventManager : WeakEventManager
{
    public static void AddHandler(Publisher source, EventHandler handler)
    {
        CurrentManager.ProtectedAddHandler(source, handler);
    }

    protected override void StartListening(object source) 
        => ((Publisher)source).EventRaised += DeliverEvent;

    protected override void StopListening(object source) 
        => ((Publisher)source).EventRaised -= DeliverEvent;
}

2. 手动弱引用实现[编辑 | 编辑源代码]

基础实现示例:

public class WeakEvent<TEventArgs>
{
    private readonly List<WeakReference<EventHandler<TEventArgs>>> _handlers = new();

    public void AddHandler(EventHandler<TEventArgs> handler)
    {
        _handlers.Add(new WeakReference<EventHandler<TEventArgs>>(handler));
    }

    public void Raise(object sender, TEventArgs e)
    {
        foreach (var weakRef in _handlers.ToArray())
        {
            if (weakRef.TryGetTarget(out var handler))
                handler(sender, e);
            else
                _handlers.Remove(weakRef); // 清理无效引用
        }
    }
}

生命周期对比[编辑 | 编辑源代码]

sequenceDiagram participant Publisher participant Subscriber participant GC as Garbage Collector Note over Publisher,Subscriber: 标准事件模式 Subscriber->>Publisher: 订阅事件(强引用) GC->>Subscriber: 无法回收(被Publisher引用) Note over Publisher,Subscriber: 弱事件模式 Subscriber->>Publisher: 订阅事件(弱引用) GC->>Subscriber: 可正常回收

实际案例[编辑 | 编辑源代码]

场景: 日志系统需要向多个临时分析器广播消息,但不应阻止分析器被回收。

public class LogSystem
{
    private readonly WeakEvent<LogEventArgs> _logEvent = new();

    public void AddListener(EventHandler<LogEventArgs> handler) 
        => _logEvent.AddHandler(handler);

    public void Log(string message)
    {
        _logEvent.Raise(this, new LogEventArgs(message));
    }
}

// 使用示例
var logger = new LogSystem();
var analyzer = new Analyzer(); // 临时对象
logger.AddListener(analyzer.HandleLog);

// 当analyzer超出作用域后,可被GC回收

数学原理[编辑 | 编辑源代码]

弱引用不影响对象可达性判定。设对象O的引用集合为R,当且仅当: O 可达rR(强引用),rO

性能考量[编辑 | 编辑源代码]

方案 优点 缺点
WeakEventManager 线程安全 依赖WPF库
手动实现 无外部依赖 需自行处理同步

最佳实践[编辑 | 编辑源代码]

  • 适用场景:
 * 长期存活对象监听短期对象
 * 插件/模块化系统
  • 避免场景:
 * 高频事件(弱引用有性能开销)
 * 必须严格保证调用顺序的场景

进阶主题[编辑 | 编辑源代码]

泛型增强实现[编辑 | 编辑源代码]

支持任意委托类型的扩展方案:

public class WeakDelegate<TDelegate> where TDelegate : Delegate
{
    private readonly WeakReference _target;
    private readonly MethodInfo _method;

    public WeakDelegate(TDelegate handler)
    {
        _target = new WeakReference(handler.Target);
        _method = handler.Method;
    }

    public TDelegate? CreateDelegate()
    {
        if (_target.Target != null)
            return (TDelegate)Delegate.CreateDelegate(typeof(TDelegate), _target.Target, _method);
        return null;
    }
}

常见问题[编辑 | 编辑源代码]

Q: 弱事件会导致事件偶尔不触发吗?
A: 会。当订阅者被回收后,对应事件将自动失效,这是设计预期行为。

Q: 如何调试弱事件相关问题?
A: 建议: 1. 使用内存分析工具检查对象存活状态 2. 添加调试日志记录事件订阅/取消订阅

通过本文,您应已掌握弱事件模式的核心概念及实现方法。这种模式在特定场景下能有效改善内存管理,但需根据实际需求谨慎选用。