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

UPDATE

我已将这里的各种答案结合到一个新问题的“确定”答案中。

原始问题

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

public class Publisher { //ValueEventArgs inherits from EventArgs public event EventHandler<ValueEventArgs> EnabledChanged; } 

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

 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 e) { subscriber.Enabled = e.Value; }; } } 

它运行良好,直到我们开始在较小的机器上使用它,当我偶尔开始:

 System.ComponentModel.Win32Exception Not enough storage is available to process this command 

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

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

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

(哦,不要再问我为什么我们一直在制作和销毁控件,这不是我的设计决定……)

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

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

编辑我必须承认我通过“回收”有问题的控件解决了这个问题。 然而,由于我使用的“关键”显然是非独特的(呜咽),因此解决方法又回来困扰我。 我刚刚在这里发现了其他链接(试过这个 – 看起来有点太弱了 – 即使目标还活着,GC也会清除代表,同样的问题与s,oɔɯǝɹ在下面回答 ), 这里 (迫使你修改发布者,并且没有“真的和匿名代表一起工作”和这里 (由Dustin Campbell引用 – 不完整)。

在我看来,我正在寻找的东西可能在语义上是不可能的 – 闭合被设计为“即使在我离开后也会流连忘返”。

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

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

您提到了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> 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 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; } } 

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

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

所以这样的事情:

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

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

我最近根据WeakReference制作了一些示例代码:

 // strongly typed weak reference public class WeakReference : WeakReference where T : class { public WeakReference(T target) : base(target) { } public WeakReference(T target, bool trackResurrection) : base(target, trackResurrection) { } public new T Target { get { return base.Target as T; } set { base.Target = value; } } } // weak referenced generic event handler public class WeakEventHandler : WeakReference> where TEventArgs : EventArgs { public WeakEventHandler(EventHandler target) : base(target) { } protected void Invoke(object sender, TEventArgs e) { if (Target != null) { Target(sender, e); } } public static implicit operator EventHandler(WeakEventHandler weakEventHandler) { if (weakEventHandler != null) { if (weakEventHandler.IsAlive) { return weakEventHandler.Invoke; } } return null; } } // weak reference common event handler public class WeakEventHandler : WeakReference { public WeakEventHandler(EventHandler target) : base(target) { } protected void Invoke(object sender, EventArgs e) { if (Target != null) { Target(sender, e); } } public static implicit operator EventHandler(WeakEventHandler weakEventHandler) { if (weakEventHandler != null) { if (weakEventHandler.IsAlive) { return weakEventHandler.Invoke; } } return null; } } // observable class, fires events public class Observable { public Observable() { Console.WriteLine("new Observable()"); } ~Observable() { Console.WriteLine("~Observable()"); } public event EventHandler OnChange; protected virtual void DoOnChange() { EventHandler handler = OnChange; if (handler != null) { Console.WriteLine("DoOnChange()"); handler(this, EventArgs.Empty); } } public void Change() { DoOnChange(); } } // observer, event listener public class Observer { public Observer() { Console.WriteLine("new Observer()"); } ~Observer() { Console.WriteLine("~Observer()"); } public void OnChange(object sender, EventArgs e) { Console.WriteLine("-> Observer.OnChange({0}, {1})", sender, e); } } // sample usage and test code public static class Program { static void Main() { Observable subject = new Observable(); Observer watcher = new Observer(); Console.WriteLine("subscribe new WeakEventHandler()\n"); subject.OnChange += new WeakEventHandler(watcher.OnChange); subject.Change(); Console.WriteLine("\nObserver = null, GC"); watcher = null; GC.Collect(0, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); subject.Change(); if (Debugger.IsAttached) { Console.Write("Press any key to continue . . . "); Console.ReadKey(true); } } } 

生成以下输出:

 new Observable() new Observer() subscribe new WeakEventHandler() DoOnChange() -> Observer.OnChange(ConsoleApplication4.Observable, System.EventArgs) Observer = null, GC ~Observer() DoOnChange() ~Observable() Press any key to continue . . . 

(请注意,取消订阅( – =)不起作用)

在Egor的答案上进一步构建,我想尝试构建一个版本,我不需要事先确定要附加哪个事件。

我只是设法使它与通用事件处理程序一起工作:对于’标准’事件处理程序(例如FormClosingEventHandler),它有点棘手,因为你不能有一个类型约束where T : delegate (除非你的名字结束于小马 )。

 private static void SetAnyGenericHandler( Action> add, //to add event listener to publisher Action> remove, //to remove event listener from publisher S subscriber, //ref to subscriber (to pass to consume) Action consume) //called when event is raised* where T : EventArgs where S : class { var subscriber_weak_ref = new WeakReference(subscriber); EventHandler handler = null; handler = delegate(object sender, T e) { var subscriber_strong_ref = subscriber_weak_ref.Target as S; if(subscriber_strong_ref != null) { Console.WriteLine("New event received by subscriber"); consume(subscriber_strong_ref, e); } else { remove(handler); handler = null; } }; add(handler); } 

(*我在这里尝试使用EventHandler consume ,但是调用代码变得很丑,因为你必须在消费lambda中将s强制转换为Subscriber。)

调用代码示例,取自上面的示例:

 SetAnyGenericHandler( h => publisher.EnabledChanged += h, h => publisher.EnabledChanged -= h, subscriber, (Subscriber s, ValueEventArgs e) => s.Enabled = e.Value); 

或者,如果您愿意

 SetAnyGenericHandler>( h => publisher.EnabledChanged += h, h => publisher.EnabledChanged -= h, subscriber, (s, e) => s.Enabled = e.Value); 

能够将事件作为一个参数传递会很好,但你不能从事件中访问添加/删除任何超过你可以从属性访问get / set的东西(我认为没有做令人讨厌的反思的东西,我认为)。