更新
我已将此处的各种答案合并到new question上的“确定”答案中。
原始问题
在我的代码中,我有一个事件发布者,它在应用程序的整个生命周期中都存在(这里简化为基本要素):
public class Publisher
{
//ValueEventArgs<T> inherits from EventArgs
public event EventHandler<ValueEventArgs<bool>> 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<bool> e)
{
subscriber.Enabled = e.Value;
};
}
}
它工作正常,直到我开始在较小的机器上使用它,当我偶尔开始使用它时:
System.ComponentModel.Win32Exception
Not enough storage is available to process this command
事实证明,代码中有一个位置可以动态创建,添加和删除表单中的订阅者控件。鉴于我对垃圾收集等的深入理解(即直到昨天都没有),我从未想过要在我身后清理,因为在绝大多数情况下,订阅者也会在应用程序的生命周期中生存。
我已经用Dustin Campbell's WeakEventHandler摆弄了一段时间,但它不适用于匿名代表(不管怎么说都不适合我)。
有没有出现这个问题?我真的希望避免在整个商店中复制粘贴样板代码。
(哦,不要再问我为什么一直在制作和销毁控件,这不是我的设计决定......)
(PS:这是一个winforms应用程序,但是我们已经升级到VS2008和.Net 3.5,我应该考虑使用Weak Event pattern吗?)
(PPS:Good answer from Rory,但是如果有人能拿出WeakEventHandler的等价物来避免我必须记住显式UnLink / Dispose,那就太酷了......)
编辑我必须承认我通过“回收”相关控件来解决这个问题。然而,由于我使用的“关键”显然是非独特的(呜咽),因此解决方法又回来困扰我。我刚刚发现了其他链接here(试过这个 - 似乎有点太弱 - 即使目标仍然存在,GC也会清除代理,下面的问题与s,oɔɯǝɹ answer相同),here(强制您修改发布者,并且不与匿名代表合作)和here(由Dustin Campbell引用为不完整)。
在我看来,我正在寻找的东西可能在语义上是不可能的 - 封闭设计为“即使在我离开后也会流连忘返”。
我找到了另一种解决方法,所以我会坚持这一点,等待voice from the gods。
答案 0 :(得分: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;
}
}
传言WPF弱事件模式带来了很多开销,所以在这种特殊情况下我不会使用它。此外,在WinForm应用程序中引用核心WPF库似乎也有点沉重。
答案 1 :(得分:4)
如果您保留对匿名委托的引用,然后在从表单中删除控件时将其删除,该控件应允许对控件和匿名委托进行垃圾回收。
这样的事情:
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;
}
}
有关删除代理的示例,请参阅Unsubscribe anonymous method in C#。
答案 2 :(得分:1)
我最近制作的一些示例代码,基于WeakReference:
// strongly typed weak reference
public class WeakReference<T> : 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<TEventArgs> : WeakReference<EventHandler<TEventArgs>>
where TEventArgs : EventArgs
{
public WeakEventHandler(EventHandler<TEventArgs> target)
: base(target)
{ }
protected void Invoke(object sender, TEventArgs e)
{
if (Target != null)
{
Target(sender, e);
}
}
public static implicit operator EventHandler<TEventArgs>(WeakEventHandler<TEventArgs> weakEventHandler)
{
if (weakEventHandler != null)
{
if (weakEventHandler.IsAlive)
{
return weakEventHandler.Invoke;
}
}
return null;
}
}
// weak reference common event handler
public class WeakEventHandler : WeakReference<EventHandler>
{
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 . . .
(请注意,取消订阅( - =)不起作用)
答案 3 :(得分:0)
在Egor's回答的基础上进一步构建,我想尝试构建一个版本,我无需提前确定我要附加哪个事件。
我只是设法使它与泛型事件处理程序一起工作:对于'标准'事件处理程序(例如FormClosingEventHandler),它有点棘手,因为你不能有类型约束where T : delegate
(除非你的名字以Pony)结尾。
private static void SetAnyGenericHandler<S, T>(
Action<EventHandler<T>> add, //to add event listener to publisher
Action<EventHandler<T>> remove, //to remove event listener from publisher
S subscriber, //ref to subscriber (to pass to consume)
Action<S, T> consume) //called when event is raised*
where T : EventArgs
where S : class
{
var subscriber_weak_ref = new WeakReference(subscriber);
EventHandler<T> 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<T> consume
,但是调用代码变得很丑,因为你必须在消费lambda中将s强制转换为Subscriber。)
调用代码示例,取自上面的示例:
SetAnyGenericHandler(
h => publisher.EnabledChanged += h,
h => publisher.EnabledChanged -= h,
subscriber,
(Subscriber s, ValueEventArgs<bool> e) => s.Enabled = e.Value);
或者,如果您愿意
SetAnyGenericHandler<Subscriber, ValueEventArgs<bool>>(
h => publisher.EnabledChanged += h,
h => publisher.EnabledChanged -= h,
subscriber,
(s, e) => s.Enabled = e.Value);
能够将事件作为一个参数传递会很好,但是你不能从事件中访问添加/删除任何超过你可以访问属性的get / set(没有做令人讨厌的反射的东西) ,我想)。