在底层使用弱引用实现C#事件是一个好主意吗?

11

我一直在思考,在适当的情况下,是否值得使用类似以下内容(粗略概念代码)来实现弱事件:

class Foo {

    private WeakEvent<EventArgs> _explodedEvent = new WeakEvent<EventArgs>();

    public event WeakEvent<EventArgs>.EventHandler Exploded {
        add { _explodedEvent += value; }
        remove { _explodedEvent -= value; }
    }

    private void OnExploded() {
        _explodedEvent.Invoke(this, EventArgs.Empty);
    }

    public void Explode() {
        OnExploded();
    }

}

使用更传统的C#语法允许其他类对事件进行订阅和取消订阅,而在内部实际上是通过弱引用来实现:

static void Main(string[] args) {
    var foo = new Foo();
    foo.Exploded += (sender, e) => Console.WriteLine("Exploded!");

    foo.Explode();
    foo.Explode();
    foo.Explode();

    Console.ReadKey();
}

WeakEvent<TEventArgs> 帮助类定义如下:

public class WeakEvent<TEventArgs> where TEventArgs : EventArgs {

    public delegate void EventHandler(object sender, TEventArgs e);

    private List<WeakReference> _handlers = new List<WeakReference>();

    public void Invoke(object sender, TEventArgs e) {
        foreach (var handler in _handlers)
            ((EventHandler)handler.Target).Invoke(sender, e);
    }

    public static WeakEvent<TEventArgs> operator + (WeakEvent<TEventArgs> e, EventHandler handler) {
        e._handlers.Add(new WeakReference(handler));
        return e;
    }

    public static WeakEvent<TEventArgs> operator - (WeakEvent<TEventArgs> e, EventHandler handler) {
        e._handlers.RemoveAll(x => (EventHandler)x.Target == handler);
        return e;
    }

}

这是一个好的方法吗?这种方法有什么不良副作用吗?


2
你有查看过 WeakEventManager 吗?https://msdn.microsoft.com/zh-cn/library/vstudio/aa970850(v=vs.100).aspx - lintmouse
转到 http://codereview.stackexchange.com? - quetzalcoatl
2
@quetzalcoatl 不是,对于那些想要关闭问题的人也不是。这个问题很适合在这个网站上讨论。 - AgentFire
几乎适用于CodeReview,但示例代码是不相关的话题。 - Kaz
2个回答

3

那是一个糟糕的想法,因为:

  1. 你的程序开始变得非确定性,因为副作用取决于GC的操作。
  2. GCHandles会带来性能成本。

请参阅链接答案。它是95%的重复,但我认为还不足以关闭问题。我将引用最相关的部分:


弱引用会导致语义上的差异和不确定性。如果将() => LaunchMissiles()连接到某个事件,有时可能会发现导弹被发射了,而有时GC已经移除了处理程序。这可以通过依赖句柄来解决,但会增加另一层复杂性。

我个人认为,事件的强引用特性很少是问题。通常,在具有相同或非常相似的生命周期的对象之间挂钩事件。例如,在ASP.NET的HTTP请求上下文中,可以随意挂钩事件,因为当请求结束时,所有都将有资格进行收集。任何泄漏的大小和持续时间都是有限的。


2

关于你的特定实现,有几点需要注意:

  1. 在调用之前,检查handler.Target的值是否为null,以免尝试使用已被处理的对象。

  2. C#对事件的使用有特殊的访问规则。除非代码具有私有访问权限,否则不能执行a.Event1 = a.Event2 + SomeOtherMethod。然而,对于委托,这是被允许的。你的实现更像是一个委托而不是一个事件。这可能不是一个主要的问题,但也需要考虑一下。

  3. 你的运算符方法应该返回一个新的对象,而不是修改第一个参数并返回它。实现operator +允许使用以下语法:a = b + c,但在你的实现中,你正在修改b的状态!这不符合人们对这些运算符工作的期望;你需要返回一个新的对象而不是修改现有的对象。(另外,由于这个原因,你的实现不是线程安全的。当一个线程正在引发事件时,从另一个线程调用运算符+会引发异常,因为在foreach期间修改了集合。)


同意运算符的问题;就像我说的那样;那只是非常粗略的;我认为我可能会使用添加和删除方法。我的问题更多地是针对这种方法是否总体上是一个好方法? - Lea Hayes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接