kidroca
kidroca

Reputation: 3856

C# One Time (Fire once) Events Implementation

I'm interested creating an event handling object that you can subscribe for one time execution only and then the action is automatically unsubscribed

Is there similar native functionality in .NET? Here is what works for me right now:

public class CustomTimer
{
    private event Action OneSecond;

    private readonly Timer timer;

    // Registered actions that should be called only once
    private readonly ICollection<Action> oneOffs;

    public CustomTimer()
    {
        this.timer = new Timer { Interval = 1000 };
        this.timer.Elapsed += this.OnOneSecond;
        this.oneOffs = new HashSet<Action>();
    }

    public bool IsRunning => this.timer.Enabled;

    public void Start()
    {
        this.timer.Start();
    }

    public void Stop()
    {
        this.timer.Stop();
    }

    public void Subscribe(Action callback)
    {
        this.OneSecond += callback;
    }

    public void SubscribeOnce(Action callback)
    {
        this.oneOffs.Add(callback);
        this.Subscribe(callback);
    }

    public void Unsubscribe(Action callback)
    {
        this.OneSecond -= callback;
        this.oneOffs.Remove(callback);
    }

    protected virtual void OnOneSecond(object sender, ElapsedEventArgs elapsedEventArgs)
    {
        this.OneSecond?.Invoke();
        this.UnsubscribeOneOffs();
    }

    private void UnsubscribeOneOffs()
    {
        if (this.oneOffs.Count > 0)
        {
            foreach (var action in this.oneOffs)
            {
                this.OneSecond -= action;
            }

            this.oneOffs.Clear();
        }
    }
}

Here the events are set to execute every second.

How can I use similar strategy in other object that trigger events unpredictably and prevent events execution while the UnsubscribeOneOffs() method is running. Should I use some kind of lock?

Upvotes: 2

Views: 2912

Answers (1)

alexm
alexm

Reputation: 6882

There is no need to register one time actions as OneSecond event handlers. Just keep them in a separate list.

public class CustomTimer
{
    List<Action> _oneTimeActions = new List<Action>();

    public void SubscribeOnce(Action handler)
    {
         lock(_oneTimeActions) 
         { 
           _oneTimeActions.Add(handler);
         }
    }


    protected virtual void OnOneSecond(object sender, ElapsedEventArgs elapsedEventArgs)
    {

          // get a local copy of scheduled one time items
          // removing them from the list. 
          Action[] oneTimers;

          lock(_oneTimeActions)
          {
              oneTimers = _oneTimeActions.ToArray();
              _oneTimeActions.Clear();
          }     

          // Execute periodic events first
          this.OneSecond?.Invoke();

          // Now execute one time actions
          foreach(var action in oneTimers)
          {
              action();
          }
    }
}  

Upvotes: 1

Related Questions