使用匿名委托进行事件处理时的垃圾收集

Ben*_*jol 13 c# garbage-collection event-handling

UPDATE

我将这里的各种答案结合到一个新问题的"确定"答案中.

原始问题

在我的代码中,我有一个事件发布者,它在应用程序的整个生命周期中都存在(这里简化为基本要素):

public class Publisher
{
    //ValueEventArgs<T> inherits from EventArgs
    public event EventHandler<ValueEventArgs<bool>> EnabledChanged; 
}
Run Code Online (Sandbox Code Playgroud)

因为这个发布者可以在所有地方使用,所以我对自己创建这个小帮助类非常满意,以避免在所有订阅者中重写处理代码:

public static class Linker
{
    public static void Link(Publisher publisher, Control subscriber)
    {
         publisher.EnabledChanged += (s, e) => subscriber.Enabled = e.Value;
    }

    //(Non-lambda version, if you're not comfortable with lambdas)
    public static void Link(Publisher publisher, Control subscriber)
    {
         publisher.EnabledChanged +=
             delegate(object sender, ValueEventArgs<bool> e)
             {
                  subscriber.Enabled = e.Value;
             };
    }
}
Run Code Online (Sandbox Code Playgroud)

它工作正常,直到我开始在较小的机器上使用它,当我偶尔开始:

System.ComponentModel.Win32Exception
Not enough storage is available to process this command
Run Code Online (Sandbox Code Playgroud)

事实证明,代码中有一个位置是从表单动态创建,添加和删除订阅者控件.鉴于我对垃圾收集等的深入理解(即没有,直到昨天),我从未想过要在我身后清理,因为在绝大多数情况下,订阅者也会在应用程序的生命周期内生存.

我和Dustin Campbell的WeakEventHandler一起摆弄了一段时间,但它不能与匿名代表合作(不管怎么说都不适合我).

反正出于这个问题了吗?我真的希望避免在整个商店中复制粘贴样板代码.

(哦,不要再问我为什么我们一直在创造和破坏控制,这不是我的设计决定......)

(PS:这是一个winforms应用程序,但是我们已经升级到VS2008和.Net 3.5,我应该考虑使用弱事件模式吗?)

(PPS:Rory的答案很好,但是如果有人能拿到WeakEventHandler的等价物来避免我必须记住显式的UnLink/Dispose,那就太酷了......)

编辑我必须承认我通过"回收"有问题的控件解决了这个问题.然而,由于我使用的"关键"显然是非独特的(呜咽),因此解决方法又回来困扰我.我刚刚发现的其他环节在这里(试过这-似乎有点过于软弱- GC清除,即使目标还活着,同样的问题与代表S,oɔɯǝɹ回答如下图),这里(逼着你修改发布,并没有按"真的和匿名代表一起工作"和这里(由Dustin Campbell引用为"不完整").

在我看来,我正在寻找的东西可能在语义上是不可能的 - 封闭设计为"即使在我离开后也会流连忘返".

我找到了另一个解决方法,所以我会坚持下去,等待众神声音.

Ror*_*ory 5

如果保留对匿名委托的引用,然后在从表单中删除控件时将其删除,则应允许对控件和匿名委托进行垃圾收集。

所以像这样:

public static class Linker
{

    //(Non-lambda version, I'm not comfortable with lambdas:)
    public static EventHandler<ValueEventArgs<bool>> Link(Publisher publisher, Control subscriber)
    {
         EventHandler<ValueEventArgs<bool>> handler = delegate(object sender, ValueEventArgs<bool> e)
             {
                  subscriber.Enabled = e.Value;
             };
         publisher.EnabledChanged += handler;
         return handler;
    }

    public static void UnLink(Publisher publisher, EventHandler<ValueEventArgs<bool>> handler)
    {
        publisher.EnabledChanged -= handler;
    }

}
Run Code Online (Sandbox Code Playgroud)

有关删除委托的示例,请参阅取消订阅 C# 中的匿名方法。


Ego*_*gor 5

我知道这个问题很古老,但是地狱 - 我找到了它,我认为其他人也可能.我正在尝试解决相关问题,并可能有一些见解.

你提到了Dustin Campbell的WeakEventHandler - 它确实无法通过设计使用匿名方法.当我意识到a)99%的情况下,我需要这样的东西,他原来的解决方案会更安全,并且b)在我需要的少数情况下(注意:有)而不是"因为lambdas是如此美丽和简洁",所以如果你有点聪明,它就有可能成功.

你的例子看起来就像是一种一次性的情况,有点棘手可以导致一个相当简洁的解决方案.


public static class Linker {
    public static void Link(Publisher publisher, Control subscriber) {
        // anonymous method references the subscriber only through weak 
        // references,so its existance doesn't interfere with garbage collection
        var subscriber_weak_ref = new WeakReference(subscriber);

        // this instance variable will stay in memory as long as the  anonymous
        // method holds a reference to it we declare and initialize  it to 
        // reserve the memory (also,  compiler complains about uninitialized
        // variable otherwise)
        EventHandler<ValueEventArgs<bool>> handler = null;

        // when the handler is created it will grab references to the  local 
        // variables used within, keeping them in memory after the function 
        // scope ends
        handler = delegate(object sender, ValueEventArgs<bool> e) {
            var subscriber_strong_ref = subscriber_weak_ref.Target as Control;

            if (subscriber_strong_ref != null) 
                subscriber_strong_ref.Enabled = e.Value;
            else {
                // unsubscribing the delegate from within itself is risky, but
                // because only one instance exists and nobody else has a
                // reference to it we can do this
                ((Publisher)sender).EnabledChanged -= handler;

                // by assigning the original instance variable pointer to null
                // we make sure that nothing else references the anonymous
                // method and it can be collected. After this, the weak
                //  reference and the handler pointer itselfwill be eligible for
                // collection as well.
                handler = null; 
            }
        };

        publisher.EnabledChanged += handler;
    }
}
Run Code Online (Sandbox Code Playgroud)

传闻WPF弱事件模式带来了很多开销,因此在这种特殊情况下我不会使用它.此外,在WinForm应用程序中引用核心WPF库似乎也有点沉重.