One shot events using Lambda in C#

C#EventsLambda

C# Problem Overview


I find myself doing this sort of thing quite often:-

 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 attach an event handler to listen for one shot from the event, I no longer want to stay attached after that. Quite often that "snippert of code" is just one line.

My mind is going a bit numb, I'm sure there must be something I can do so I don't need to repeat all this overhead. Bear in mind that EventHandler may well be EventHandler<T>.

Any ideas how I can tidy up the repeative part of the code and just leave the snippet in a Lambda?

C# Solutions


Solution 1 - C#

You could attache a permanent event handler to the event. The event handler then invokes "one shot event handlers" that are added to an 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();

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

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

Solution 2 - C#

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) => {
      eventInfo.RemoveEventHandler(eventSource, internalHandler);
      handler(src, args);
    };
    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) => {
      eventInfo.RemoveEventHandler(eventSource, internalHandler);
      handler(src, args);
    };
    eventInfo.AddEventHandler(eventSource, internalHandler);
  }

}

Use it like so:

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!"));

Solution 3 - C#

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

You can make an Observable from an event, and only listen for the first element using .Take(1), to do your small snippet of code. This turns this entire process into a couple of lines of code.


Edit: In order to demonstrate, I've made a full sample program (I'll paste below).

I moved the observable creation and subscription into a method (HandleOneShot). This lets you do what you're attempting with a single method call. For demonstrating, I made a class with two properties that implements INotifyPropertyChanged, and am listening for the first property changed event, writing to the console when it occurs.

This takes your code, and changes it to:

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

Notice that all of the subscription/unsubscription happens automatically for you behind the scenes. There's no need to handle putting in the subscription manually - just Subscribe to the Observable, and Rx takes care of this for you.

When run, this code prints:

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

You only get a single, one shot 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();
        }
    }
}

Solution 4 - C#

Another user encountered https://stackoverflow.com/questions/1554845/am-i-using-the-right-approach-to-monitor-the-tasks-i-want-to-perform-when-a-handl/1554978#1554978">a very similar problem, and I believe the solution in that thread applies here.

In particular, what you have is not an instance of the publish/subscribe pattern, its a message queue. Its easy enough to create your own message queue using a Queue{EventHandler}, where you dequeue events as you invoke them.

So instead of hooking on to an event handler, your "one-shot" events should expose a method allowing clients to add an function to the message queue.

Solution 5 - C#

Does it work? If so, then I say go for it. For a one-shot event that looks to be quite elegant.

What I like...

  • If s is garbage collected, so will the event handler.
  • The detaching code is right next to the attaching code, making it easy to see what you are are doing.

You might be able to generalize it, but I'm not entierly sure how to because I can't seem to get a pointer to a event.

Solution 6 - C#

Personally, I just create a specialized extension method for whatever type has the event I'm dealing with.

Here's a basic version of something I am 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;
		}
	}
}

The reason I think this is the best solution is because you often don't need to just handle the event one time. You also often need to check if the event has already passed... For instance, here is another version of the above extension method that uses an attached property to check if the element is already loaded, in which case it just calls the given handler right away:

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

Solution 7 - C#

You probably want to work with the new async/await idioms. Usually when I need to execute an event handler one-shot like you described, what I really need is something like:

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

Solution 8 - C#

Why not do 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);
        }
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAnthonyWJonesView Question on Stackoverflow
Solution 1 - C#dtbView Answer on Stackoverflow
Solution 2 - C#JordãoView Answer on Stackoverflow
Solution 3 - C#Reed CopseyView Answer on Stackoverflow
Solution 4 - C#JulietView Answer on Stackoverflow
Solution 5 - C#Jonathan AllenView Answer on Stackoverflow
Solution 6 - C#Wayne BlossView Answer on Stackoverflow
Solution 7 - C#Albino CordeiroView Answer on Stackoverflow
Solution 8 - C#belayonView Answer on Stackoverflow