与lambdas一起使用的弱事件处理程序模型

好的,所以这个问题不仅仅是一个问题,而是在回答这个问题 ,把Dustin Campbell , Egor的各个部分和IObservable / Rx / Reactive框架的最后一个提示放在一起之后,我想我为这个特殊问题找出了一个可行的解决scheme。 它可能完全被IObservable / Rx / Reactive框架所取代,但只有经验才会显示出来。

我故意创造了一个新的问题,给我一些空间来解释我是如何得到这个解决scheme的,因为它可能不是很明显。

有很多相关的问题,大多数告诉你,如果你想能够在以后分离它们,你不能使用内联lambdas:

  • .Net中的弱事件?
  • 在C#中使用lambdas解除事件
  • 可以使用lambda作为事件处理程序导致内存泄漏?
  • 如何取消订阅使用lambdaexpression式的事件?
  • 在C#中取消订阅匿名方法

事实上,如果希望能够在以后分离它们,你需要保留对lambda的引用。 但是,如果您只是希望事件处理程序在订阅者超出范围时自行分离,则此答案适用于您。

'答案

(如果你想看看我如何得到这个解决scheme,请阅读更多内容)

用法,给定一个vanilla MouseDown事件的控件,以及一个特定的EventHandler<ValueEventArgs> ValueEvent事件:

 // for 'vanilla' events SetAnyHandler<Subscriber, MouseEventHandler, MouseEventArgs>( h => (o,e) => h(o,e), //don't ask me, but it works*. h => control.MouseDown += h, h => control.MouseDown -= h, subscriber, (s, e) => s.DoSomething(e)); //**See note below // for generic events SetAnyHandler<Subscriber, ValueEventArgs>( h => control.ValueEvent += h, h => control.ValueEvent -= h, subscriber, (s, e) => s.DoSomething(e)); //**See note below 

(*这是来自Rx的解决方法)

(**)避免直接在这里调用订阅对象(例如放入subscriber.DoSomething(e)),或者如果我们在Subscriber类中直接调用DoSomething(e)是非常重要的,这样做有效地创build了对订阅者的引用,彻底击败对象…)

注意 :在某些情况下,这个CAN会引用为内存中lambdaexpression式创build的包装类,但是它们只是称量字节,所以我不太感到困扰。

执行:

 //This overload handles any type of EventHandler public static void SetAnyHandler<S, TDelegate, TArgs>( Func<EventHandler<TArgs>, TDelegate> converter, Action<TDelegate> add, Action<TDelegate> remove, S subscriber, Action<S, TArgs> action) where TArgs : EventArgs where TDelegate : class where S : class { var subs_weak_ref = new WeakReference(subscriber); TDelegate handler = null; handler = converter(new EventHandler<TArgs>( (s, e) => { var subs_strong_ref = subs_weak_ref.Target as S; if(subs_strong_ref != null) { action(subs_strong_ref, e); } else { remove(handler); handler = null; } })); add(handler); } // this overload is simplified for generic EventHandlers public static void SetAnyHandler<S, TArgs>( Action<EventHandler<TArgs>> add, Action<EventHandler<TArgs>> remove, S subscriber, Action<S, TArgs> action) where TArgs : EventArgs where S : class { SetAnyHandler<S, EventHandler<TArgs>, TArgs>( h => h, add, remove, subscriber, action); } 

细节

我的出发点是叶戈尔的出色答案(见评论版链接):

 public static void Link(Publisher publisher, Control subscriber) { var subscriber_weak_ref = new WeakReference(subscriber); EventHandler<ValueEventArgs<bool>> handler = null; 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 { ((Publisher)sender).EnabledChanged -= handler; handler = null; } }; publisher.EnabledChanged += handler; } 

困扰我的是这个事件被硬编码到了这个方法中。 所以这意味着每个新事件都有一个新的写法。

我摆弄着,并设法提出这个通用的解决scheme:

 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 action) Action<S, T> action) //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"); action(subscriber_strong_ref, e); } else { remove(handler); handler = null; } }; add(handler); } 

然而,该解决scheme的问题是,它只是通用的,它不能处理标准的winforms MouseUp,MouseDown等…

所以我试图让它通用:

 private static void SetAnyHandler<T, R>( Action<T> add, //to add event listener to publisher Action<T> remove, //to remove event listener from publisher Subscriber subscriber, //ref to subscriber (to pass to action) Action<Subscriber, R> action) where T : class { var subscriber_weak_ref = new WeakReference(subscriber); T handler = null; handler = delegate(object sender, R e) //<-compiler doesn't like this line { var subscriber_strong_ref = subscriber_weak_ref.Target as Subscriber; if(subscriber_strong_ref != null) { action(subscriber_strong_ref, e); } else { remove(handler); handler = null; } }; remove(handler); } 

但是,正如我在这里暗示的那样,这是不会编译的,因为没有办法限制T成为代表。

那时,我几乎放弃了。 试图与C#规范抗争是没有意义的。

但是,昨天我从Reactive框架中发现了Observable.FromEvent方法,我没有实现,但是用法看起来有点儿熟悉,而且非常有趣:

 var mousedown = Observable.FromEvent<MouseEventHandler, MouseDownEventArgs>( h => new MouseEventHandler(h), h => control.MouseDown += h, h => control.MouseDown -= h); 

这是引起我注意的第一个论据。 这是没有委托types约束的解决方法。 我们通过传递将创build委托的函数来接受它。

把所有这些放在一起给了我们解决scheme显示在这个答案的顶部。

事后

我彻底build议花时间了解反应框架(或者最终被调用)。 这是非常有趣的,有点mindblowing。 我怀疑这也会造成这样的问题完全是多余的。

到目前为止,我见过的最有趣的东西是Channel9上的video。

如果你转向CodePlex,那么有一个名为Sharp Observation的项目,其中作者构build了一个很好的弱代理提供者,在MSIL中实现。 快速,灵活,易于使用:例如

 Action<int,int> myDelegate = new Action<int,int>( aMethodOnMyClass ); myDelegate.MakeWeak(); 

就这么简单!

我一直在寻找一个解决scheme很长一段时间,大多数使用讨厌的反思,但Benjohl的答案是伟大的。 我已经调整了它,添加了对非genericsEventHandler,不从EventArgsinheritance的DependencyPropertyChangedEventArgs的支持,并允许您自己手动注销一个事件。 我会对人们的思想非常感兴趣,尤其是Benjohl。

 /// <summary> /// Weakly registers for events using <see cref="WeakReference"/>. /// </summary> public sealed class WeakEvent { private Action removeEventHandler; /// <summary> /// Initializes a new instance of the <see cref="WeakEvent"/> class. /// </summary> /// <param name="removeEventHandler">The remove event handler function.</param> private WeakEvent(Action removeEventHandler) { this.removeEventHandler = removeEventHandler; } /// <summary> /// Weakly registers the specified subscriber to the the given event of type /// <see cref="EventHandler"/>. /// </summary> /// <example> /// Application application; /// WeakEvent.Register{TextBox, TextChangedEventArgs>( /// this, /// eventHandler => textBox.TextChanged += eventHandler, /// eventHandler => textBox.TextChanged -= eventHandler, /// (sender, e) => this.OnTextChanged(sender, e)); /// </example> /// <typeparam name="S">The type of the subscriber.</typeparam> /// <param name="subscriber">The subscriber.</param> /// <param name="addEventhandler">The add eventhandler.</param> /// <param name="removeEventHandler">The remove event handler function.</param> /// <param name="action">The event execution function.</param> public static WeakEvent Register<S>( S subscriber, Action<EventHandler> addEventhandler, Action<EventHandler> removeEventHandler, Action<S, EventArgs> action) where S : class { return Register<S, EventHandler, EventArgs>( subscriber, eventHandler => (sender, e) => eventHandler(sender, e), addEventhandler, removeEventHandler, action); } /// <summary> /// Weakly registers the specified subscriber to the the given event of type /// <see cref="EventHandler{T}"/>. /// </summary> /// <example> /// Application application; /// WeakEvent.Register{TextBox, TextChangedEventArgs>( /// this, /// eventHandler => textBox.TextChanged += eventHandler, /// eventHandler => textBox.TextChanged -= eventHandler, /// (sender, e) => this.OnTextChanged(sender, e)); /// </example> /// <typeparam name="S">The type of the subscriber.</typeparam> /// <typeparam name="TEventArgs">The type of the event arguments.</typeparam> /// <param name="subscriber">The subscriber.</param> /// <param name="addEventhandler">The add eventhandler.</param> /// <param name="removeEventHandler">The remove event handler function.</param> /// <param name="action">The event execution function.</param> public static WeakEvent Register<S, TEventArgs>( S subscriber, Action<EventHandler<TEventArgs>> addEventhandler, Action<EventHandler<TEventArgs>> removeEventHandler, Action<S, TEventArgs> action) where S : class where TEventArgs : EventArgs { return Register<S, EventHandler<TEventArgs>, TEventArgs>( subscriber, eventHandler => eventHandler, addEventhandler, removeEventHandler, action); } /// <summary> /// Weakly registers the specified subscriber to the the given event. /// </summary> /// <example> /// TextBox textbox; /// WeakEvent.Register{TextBox, TextChangedEventHandler, TextChangedEventArgs>( /// this, /// eventHandler => (sender, e) => eventHandler(sender, e), /// eventHandler => textBox.TextChanged += eventHandler, /// eventHandler => textBox.TextChanged -= eventHandler, /// (sender, e) => this.OnTextChanged(sender, e)); /// </example> /// <typeparam name="S">The type of the subscriber.</typeparam> /// <typeparam name="TEventHandler">The type of the event handler.</typeparam> /// <typeparam name="TEventArgs">The type of the event arguments.</typeparam> /// <param name="subscriber">The subscriber.</param> /// <param name="getEventHandler">The get event handler function.</param> /// <param name="addEventHandler">The add event handler function.</param> /// <param name="removeEventHandler">The remove event handler function.</param> /// <param name="action">The event execution function.</param> public static WeakEvent Register<S, TEventHandler, TEventArgs>( S subscriber, Func<EventHandler<TEventArgs>, TEventHandler> getEventHandler, Action<TEventHandler> addEventHandler, Action<TEventHandler> removeEventHandler, Action<S, TEventArgs> action) where S : class where TEventHandler : class where TEventArgs : EventArgs { WeakReference weakReference = new WeakReference(subscriber); TEventHandler eventHandler = null; eventHandler = getEventHandler( new EventHandler<TEventArgs>( (sender, e) => { S subscriberStrongRef = weakReference.Target as S; if (subscriberStrongRef != null) { action(subscriberStrongRef, e); } else { removeEventHandler(eventHandler); eventHandler = null; } })); addEventHandler(eventHandler); return new WeakEvent(() => removeEventHandler(eventHandler)); } public static WeakEvent Register<S>( S subscriber, Action<DependencyPropertyChangedEventHandler> addEventHandler, Action<DependencyPropertyChangedEventHandler> removeEventHandler, Action<S, DependencyPropertyChangedEventArgs> action) where S : class { WeakReference weakReference = new WeakReference(subscriber); DependencyPropertyChangedEventHandler eventHandler = null; eventHandler = new DependencyPropertyChangedEventHandler( (sender, e) => { S subscriberStrongRef = weakReference.Target as S; if (subscriberStrongRef != null) { action(subscriberStrongRef, e); } else { removeEventHandler(eventHandler); eventHandler = null; } }); addEventHandler(eventHandler); return new WeakEvent(() => removeEventHandler(eventHandler)); } /// <summary> /// Manually unregisters this instance from the event. /// </summary> public void Unregister() { if (this.removeEventHandler != null) { this.removeEventHandler(); this.removeEventHandler = null; } } } 

达斯汀·坎贝尔的做法已经非常出色。 剩下的唯一东西,保存一个集成到.NET中的解决scheme,是创build真正通用的弱事件处理程序的一个非常简单的方法:

http://puremsil.wordpress.com/2010/05/03/generic-weak-event-handlers/