在C#中使用Lambda的一次性事件

我发现自己经常这样做: –

EventHandler eh = null; //can't assign lambda directly since it uses eh eh = (s, args) => { //small snippet of code here ((SomeType)s).SomeEvent -= eh; } variableOfSomeType.SomeEvent += eh; 

基本上我只想附加一个事件处理程序来监听事件的一个镜头,在此之后,我不再想留下来。 很多时候,“代码snippert”只是一行。

我的头脑有点麻木,我确定必须有一些我可以做的事情,所以我不需要重复所有这些开销。 请记住, EventHandler可能是EventHandler<T>

任何想法,我可以整理代码的重复部分,只是将代码段留在Lambda中?

你可以附加一个永久性的事件处理程序。 事件处理程序然后调用添加到内部队列中的“一次性事件处理程序”:

 OneShotHandlerQueue<EventArgs> queue = new OneShotHandlerQueue<EventArgs>(); Test test = new Test(); // attach permanent event handler test.Done += queue.Handle; // add a "one shot" event handler queue.Add((sender, e) => Console.WriteLine(e)); test.Start(); // add another "one shot" event handler queue.Add((sender, e) => Console.WriteLine(e)); test.Start(); 

码:

 class OneShotHandlerQueue<TEventArgs> where TEventArgs : EventArgs { private ConcurrentQueue<EventHandler<TEventArgs>> queue; public OneShotHandlerQueue() { this.queue = new ConcurrentQueue<EventHandler<TEventArgs>>(); } public void Handle(object sender, TEventArgs e) { EventHandler<TEventArgs> handler; if (this.queue.TryDequeue(out handler) && (handler != null)) handler(sender, e); } public void Add(EventHandler<TEventArgs> handler) { this.queue.Enqueue(handler); } } 

testing课:

 class Test { public event EventHandler Done; public void Start() { this.OnDone(new EventArgs()); } protected virtual void OnDone(EventArgs e) { EventHandler handler = this.Done; if (handler != null) handler(this, e); } } 

你可以使用reflection:

 public static class Listener { public static void ListenOnce(this object eventSource, string eventName, EventHandler handler) { var eventInfo = eventSource.GetType().GetEvent(eventName); EventHandler internalHandler = null; internalHandler = (src, args) => { handler(src, args); eventInfo.RemoveEventHandler(eventSource, internalHandler); }; eventInfo.AddEventHandler(eventSource, internalHandler); } public static void ListenOnce<TEventArgs>(this object eventSource, string eventName, EventHandler<TEventArgs> handler) where TEventArgs : EventArgs { var eventInfo = eventSource.GetType().GetEvent(eventName); EventHandler<TEventArgs> internalHandler = null; internalHandler = (src, args) => { handler(src, args); eventInfo.RemoveEventHandler(eventSource, internalHandler); }; eventInfo.AddEventHandler(eventSource, internalHandler); } } 

像这样使用它:

 variableOfSomeType.ListenOnce("SomeEvent", (s, args) => Console.WriteLine("I should print only once!")); variableOfSomeType.ListenOnce<InterestingEventArgs>("SomeOtherEvent", (s, args) => Console.WriteLine("I should print only once!")); 

如果您可以使用.NET的Reactive Extensions ,则可以简化此操作。

你可以从一个事件中创build一个Observable ,并且仅仅使用.Take(1)来听第一个元素,来完成你的一小段代码。 这将整个过程转换成几行代码。


编辑:为了演示,我做了一个完整的示例程序(我将在下面粘贴)。

我将可观察的创build和订阅移到了一个方法( HandleOneShot )中。 这可以让你做一个单一的方法调用尝试。 为了演示,我创build了一个具有两个实现INotifyPropertyChanged属性的类,并且正在侦听第一个属性更改的事件,并在发生时写入控制台。

这需要您的代码,并将其更改为:

 HandleOneShot<SomeEventArgs>(variableOfSomeType, "SomeEvent", e => { // Small snippet of code here }); 

注意,所有的订阅/取消订阅都会在幕后自动发生。 没有必要手动处理订阅 – 只需订阅Observable,Rx就可以为您提供帮助。

运行时,此代码打印:

 Setup... Setting first property... **** Prop2 Changed! /new val Setting second property... Setting first property again. Press ENTER to continue... 

你只能得到一个单一的触发你的事件。

 namespace ConsoleApplication1 { using System; using System.ComponentModel; using System.Linq; class Test : INotifyPropertyChanged { private string prop2; private string prop; public string Prop { get { return prop; } set { if (prop != value) { prop = value; if (PropertyChanged!=null) PropertyChanged(this, new PropertyChangedEventArgs("Prop")); } } } public string Prop2 { get { return prop2; } set { if (prop2 != value) { prop2 = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Prop2")); } } } public event PropertyChangedEventHandler PropertyChanged; } class Program { static void HandleOneShot<TEventArgs>(object target, string eventName, Action<TEventArgs> action) where TEventArgs : EventArgs { var obsEvent = Observable.FromEvent<TEventArgs>(target, eventName).Take(1); obsEvent.Subscribe(a => action(a.EventArgs)); } static void Main(string[] args) { Test test = new Test(); Console.WriteLine("Setup..."); HandleOneShot<PropertyChangedEventArgs>( test, "PropertyChanged", e => { Console.WriteLine(" **** {0} Changed! {1}/{2}!", e.PropertyName, test.Prop, test.Prop2); }); Console.WriteLine("Setting first property..."); test.Prop2 = "new value"; Console.WriteLine("Setting second property..."); test.Prop = "second value"; Console.WriteLine("Setting first property again..."); test.Prop2 = "other value"; Console.WriteLine("Press ENTER to continue..."); Console.ReadLine(); } } } 

另一个用户遇到了一个非常类似的问题 ,我相信这个线程的解决scheme适用于这里。

特别是,你拥有的不是发布/订阅模式的一个实例,它是一个消息队列。 使用Queue{EventHandler}创build自己的消息队列非常简单,您可以在其中调用事件时将队列出队。

所以,不要挂钩到一个事件处理程序,你的“一次性”事件应该公开一个允许客户端添加一个函数到消息队列的方法。

它工作吗? 如果是这样,那么我说去吧。 对于看起来相当优雅的一次性事件。

我喜欢…

  • 如果s是垃圾回收,事件处理程序也是如此。
  • 分离代码就在附加代码旁边,很容易看到你在做什么。

你可能会推广它,但我并不确定如何去做,因为我似乎无法得到一个事件的指针。

就个人而言,我只是创build一个专门的扩展方法,无论我正在处理的事件types。

以下是我现在正在使用的基本版本:

 namespace MyLibrary { public static class FrameworkElementExtensions { public static void HandleWhenLoaded(this FrameworkElement el, RoutedEventHandler handler) { RoutedEventHandler wrapperHandler = null; wrapperHandler = delegate { el.Loaded -= wrapperHandler; handler(el, null); }; el.Loaded += wrapperHandler; } } } 

我认为这是最好的解决scheme的原因是因为你经常不需要一次处理事件。 您还经常需要检查事件是否已经通过…例如,这里是另一个版本的上述扩展方法,使用附加属性来检查元素是否已经加载,在这种情况下,它只是调用给定的处理程序马上:

 namespace MyLibraryOrApplication { public static class FrameworkElementExtensions { public static void HandleWhenLoaded(this FrameworkElement el, RoutedEventHandler handler) { if ((bool)el.GetValue(View.IsLoadedProperty)) { // el already loaded, call the handler now. handler(el, null); return; } // el not loaded yet. Attach a wrapper handler that can be removed upon execution. RoutedEventHandler wrapperHandler = null; wrapperHandler = delegate { el.Loaded -= wrapperHandler; el.SetValue(View.IsLoadedProperty, true); handler(el, null); }; el.Loaded += wrapperHandler; } } } 

你可能想要使用新的asynchronous/等待成语。 通常当我需要像你所描述的一样执行一个事件处理程序时,我真正需要的是类似于:

 await variableOfSomeSort.SomeMethodAsync(); //small snippet of code here 

为什么不使用内置到事件中的委托堆栈? 就像是…

  private void OnCheckedIn(object sender, Session e) { EventHandler<Session> nextInLine = null; lock (_syncLock) { if (SessionCheckedIn != null) { nextInLine = (EventHandler<Session>)SessionCheckedIn.GetInvocationList()[0]; SessionCheckedIn -= nextInLine; } } if ( nextInLine != null ) { nextInLine(this, e); } }