简体   繁体   English

在引擎盖下引用弱引用的C#事件是一个好主意吗?

[英]Is it a good idea to implement a C# event with a weak reference under the hood?

I have been wondering whether it would be worth implementing weak events (where they are appropriate) using something like the following (rough proof of concept code): 我一直想知道是否值得使用类似下面的内容(粗略的概念证明代码)来实现弱事件(它们是合适的):

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();
    }

}

Allowing other classes to subscribe and unsubscribe from events with the more conventional C# syntax whilst under the hood actually being implemented with weak references: 允许其他类使用更常规的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();
}

Where the WeakEvent<TEventArgs> helper class is defined as follows: 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;
    }

}

Is this a good approach? 这是一个好方法吗? are there any undesirable side effects to this approach? 这种方法有什么不良副作用吗?

That's a bad idea because: 这是一个坏主意,因为:

  1. Your program starts to become non-deterministic because side-effects depend on the actions of the GC. 您的程序开始变得不确定,因为副作用取决于GC的操作。
  2. GCHandles come at a performance cost. GCHandles的性能成本。

See the linked answer. 查看链接的答案。 It's a 95% duplicate but not quite enough to close the question I think. 这是95%的重复但不足以结束我认为的问题。 I'll quote the most relevant parts: 我会引用最相关的部分:


There also is a semantic difference and non-determinism that would be caused by weak references. 还存在由弱引用引起的语义差异非确定性 If you hook up () => LaunchMissiles() to some event you might find the missiles to be launched just sometimes. 如果您将() => LaunchMissiles()挂钩到某个事件,您可能会发现有时会启动导弹。 Other times the GC has already taken away the handler. 其他时候GC已经带走了处理程序。 This could be solved with dependent handles which introduce yet another level of complexity. 这可以通过引入另一个复杂程度的从属句柄来解决。

I personally find it rare that the strong referencing nature of events is a problem. 我个人觉得很少有事件的强烈参考性质是一个问题。 Often, events are hooked up between objects that have the same or very similar lifetime. 通常,事件连接在具有相同或非常相似的生命周期的对象之间。 For example you can hook up events all you want in the context of an HTTP request in ASP.NET because everything will be eligible for collection when the request has ended. 例如,您可以在ASP.NET中的HTTP请求的上下文中挂接所有您想要的事件,因为在请求结束时, 所有内容都有资格进行收集。 Any leaks are bounded in size and short lived. 任何泄漏的大小都是有限的,而且寿命很短。

A few comments about your particular implementation: 关于您的特定实现的一些评论:

  1. Check the value of handler.Target for null before invoking it so that you don't try to do it with an object that has been disposed. 在调用它之前检查handler.Target的值是否为null ,这样就不会尝试使用已经处置的对象来执行它。

  2. C# has special access rules for how you can use events. C#具有关于如何使用事件的特殊访问规则。 You cannot do a.Event1 = a.Event2 + SomeOtherMethod unless that code has private access to the events. 除非该代码具有对事件的私有访问权限,否则您不能执行a.Event1 = a.Event2 + SomeOtherMethod This is allowed for delegates however. 但是,这对于代表来说是允许的。 Your implementation behaves much more like a delegate instead of an event. 您的实现更像是委托而不是事件。 This is probably not a major concern but something to think about. 这可能不是一个主要的问题,但需要考虑的事情。

  3. Your operator methods should return a new object instead of modifying the first argument and returning it. 您的operator方法应返回一个新对象,而不是修改第一个参数并返回它。 Implementing operator + allows for syntax like the following: a = b + c , but in your implementation, you are modifying the state of b !. 实现operator +允许使用如下语法: a = b + c ,但在您的实现中,您正在修改b !的状态。 This is not kosher for how one would expect these operators to work; 对于人们如何期望这些运营商的工作,这不是犹太人; you need to return a new object instead of modifying the existing one. 您需要返回一个新对象而不是修改现有对象。 (Also, because of this your implementation is not thread-safe. Calling operator + from one thread while another was raising the event would raise an exception because the collection was modified during the foreach.) (另外,因此你的实现不是线程安全的。从另一个线程调用operator +而另一个线程引发事件会引发异常,因为在foreach期间修改了集合。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM