One-time events using Lambda in C #

I often do such things: -

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; 

Basically, I only want to bind an event handler to listen to one snapshot from the event, after that I no longer want to stay attached. Quite often, "snippert of code" is just one line.

My mind is a little numb, I'm sure I have to do something, so I don’t need to repeat all this overhead. Keep in mind that an EventHandler may be an EventHandler<T> .

Any ideas how I can remove the duplicate part of the code and just leave a snippet in Lambda?

+43
c # lambda events
Jan 27
source share
8 answers

You can attaché a persistent event handler. The event handler then calls the "single shot event handlers", which are added to the internal queue:

 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(); 

the code:

 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:

 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); } } 
+7
Jan 27
source share

You can use 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); } } 

Use it like this:

 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!")); 
+7
Jan 28 '10 at 2:58 p.m.
source share

If you can use Reactive Extensions for.NET , you can simplify this.

You can make an Observable from an event and listen only to the first element with .Take(1) to make your own small piece of code. This turns this whole process into a couple of lines of code.




Edit: To demonstrate, I made a complete sample program (I will embed below).

I moved the observed creation and subscription to the method ( HandleOneShot ). This allows you to do what you are trying with a single method call. For demonstration, I created a class with two properties that implements INotifyPropertyChanged, and I listen to the event with the first property changed, writing it to the console when this happens.

This takes your code and changes it to:

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

Please note that all subscription / unsubscribing is done automatically for you backstage. There is no need to process the subscription manually - just subscribe to the Observable and Rx will take care of this for you.

When launched, this code prints:

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

You receive only one trigger of your event.

 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(); } } } 
+5
Jan 27 '10 at
source share

Another user encountered a very similar problem , and I believe that the solution in this thread is applied here.

In particular, you do not have an instance of the publication / subscription template, its message queue. Its quite easy to create your own message queue using Queue{EventHandler} , where you deactivate events when they are called.

Therefore, instead of connecting to an event handler, your one-shot events should expose a method that allows clients to add a function to the message queue.

+2
Jan 27
source share

It works? If so, then I say, follow him. For the one-shot case, which looks pretty elegant.

What I like...

  • If s is garbage collection, then there will be an event handler.
  • The disconnect code is located next to the attachment code, making it easy to see what you are doing.

You may be able to generalize it, but I'm not sure how to do it, because I cannot get a pointer to the event.

+1
Jan 27 '10 at 22:14
source share

Personally, I just create a specialized extension method for any type I'm dealing with.

Here is the basic version of what I'm using right now:

 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; } } } 

I think this is the best solution, because you often do not just need to process the event once. You also often need to check if an event has passed ... For example, here is another version of the aforementioned extension method that uses an attached property to check if this element is already loaded, in which case it simply calls this handler immediately:

 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; } } } 
0
Oct 28 '10 at 17:39
source share

You will probably want to work with the new async / wait idioms. Usually, when I need to execute an event handler with a single shot, as you described, I really need something like:

 await variableOfSomeSort.SomeMethodAsync(); //small snippet of code here 
0
Nov 20 '12 at 16:20
source share

Why not use the delegate stack built into the event? Something like...

  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); } } 
0
Mar 10 '17 at 8:07
source share



All Articles