Skip to content

Instantly share code, notes, and snippets.

@Hotrian
Created October 20, 2022 21:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Hotrian/6658508143e79af0271f62adf20ffdb9 to your computer and use it in GitHub Desktop.
Save Hotrian/6658508143e79af0271f62adf20ffdb9 to your computer and use it in GitHub Desktop.

The code given here is an example and not for production usage.

See the blog entry for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Hotrian.com_Tutorials
{
public class Event
{
/// <summary> Indicates whether this event has been fired at least one time since it was last <see cref="Reset"/>. </summary>
public bool Fired { get; private set; }
private readonly SemaphoreSlim _semaphoreSlim = new(1, 1);
private readonly List<object> _oneTimeActions = new();
private readonly Dictionary<object, Func<Task>> _actions = new();
/// <summary>
/// Adds <paramref name="receiver"/> to the list of subscribers for this event.<br/>
/// When the <see cref="RaiseEvent"/> method is called, any subscribed <paramref name="action"/>s will be invoked.<br/>
/// </summary>
public async Task Subscribe(object receiver, Func<Task> action)
{
await _semaphoreSlim.WaitAsync();
try
{
if (receiver == null || action == null) return;
if (!_actions.ContainsKey(receiver))
{
_actions.Add(receiver, action);
return;
}
_actions[receiver] = action;
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary>
/// Adds <paramref name="receiver"/> to the list of subscribers for this event.<br/>
/// When the <see cref="RaiseEvent"/> method is called, any subscribed <paramref name="action"/>s will be invoked.<br/>
/// </summary>
/// <remarks>
/// If the event has already been raised at least once, the <paramref name="action"/> will automatically be invoked.<br/>
/// This is useful for synchronization, such as state data which may not be available until the event has been raised at least once.<br/>
/// Using the <see cref="SubscribeAndDo"/> method we can subscribe to be notified as soon as state data is available,<br/>
/// and invoke the <paramref name="action"/> as soon as it is, invoking immediately if it already available.
/// </remarks>
public async Task SubscribeAndDo(object receiver, Func<Task> action)
{
await _semaphoreSlim.WaitAsync();
try
{
if (receiver == null || action == null) return;
if (!_actions.ContainsKey(receiver))
{
_actions.Add(receiver, action);
if (!Fired) return;
await action.Invoke();
}
_actions[receiver] = action;
if (!Fired) return;
await action.Invoke();
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary>
/// Adds <paramref name="receiver"/> to the list of subscribers for this event.<br/>
/// When the <see cref="RaiseEvent"/> method is called, any subscribed <paramref name="action"/>s will be invoked.<br/>
/// After the <see cref="RaiseEvent"/> method is called, this <paramref name="action"/> will be unsubscribed automatically.<br/>
/// </summary>
/// <remarks>
/// If the event has already been raised at least once, the <paramref name="action"/> will automatically be invoked.<br/>
/// This is useful for synchronization, such as state data which may not be available until the event has been raised at least once.<br/>
/// Using the <see cref="SubscribeAndDo"/> method we can subscribe to be notified as soon as state data is available,<br/>
/// and invoke the <paramref name="action"/> as soon as it is, invoking immediately if it already available.<br/>
/// Using the <see cref="SubscribeAndDoOnce"/> method we can subscribe just like <br/>
/// using the <see cref="SubscribeAndDo"/> method, but our action will also be automatically unsubscribed once it is called.
/// </remarks>
public async Task SubscribeAndDoOnce(object receiver, Func<Task> action)
{
await _semaphoreSlim.WaitAsync();
try
{
if (receiver == null || action == null) return;
if (Fired)
{
await action.Invoke();
return;
}
if (!_oneTimeActions.Contains(receiver))
_oneTimeActions.Add(receiver);
if (!_actions.ContainsKey(receiver))
{
_actions.Add(receiver, action);
return;
}
_actions[receiver] = action;
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary> Removes <paramref name="receiver"/> from the list of subscribers for this event. </summary>
public async Task Unsubscribe(object receiver)
{
await _semaphoreSlim.WaitAsync();
try
{
if (!_actions.ContainsKey(receiver)) return;
_actions.Remove(receiver);
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary> Raises this event, invoking any actions for receivers which are currently subscribed to this event. </summary>
public async Task RaiseEvent()
{
await _semaphoreSlim.WaitAsync();
try
{
Fired = true;
foreach (var action in _actions.Where(action => action.Value != null))
{
await action.Value.Invoke();
}
foreach (var receiver in _oneTimeActions.Where(receiver => _actions.ContainsKey(receiver)))
{
_actions.Remove(receiver);
}
_oneTimeActions.Clear();
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary> Resets this event back to the default unfired state, setting the <see cref="Fired"/> property back to false. </summary>
public async Task Reset()
{
await _semaphoreSlim.WaitAsync();
try
{
Fired = false;
}
finally
{
_semaphoreSlim.Release();
}
}
}
public class Event<T>
{
/// <summary> Indicates whether this event has been fired at least one time since it was last <see cref="Reset"/>. </summary>
public bool Fired { get; private set; }
public T LastValue { get; private set; }
private readonly SemaphoreSlim _semaphoreSlim = new(1, 1);
private readonly List<object> _oneTimeActions = new();
private readonly Dictionary<object, Func<T, Task>> _actions = new();
/// <summary>
/// Adds <paramref name="receiver"/> to the list of subscribers for this event.<br/>
/// When the <see cref="RaiseEvent"/> method is called, any subscribed <paramref name="action"/>s will be invoked.<br/>
/// </summary>
public async Task Subscribe(object receiver, Func<T, Task> action)
{
await _semaphoreSlim.WaitAsync();
try
{
if (receiver == null || action == null) return;
if (!_actions.ContainsKey(receiver))
{
_actions.Add(receiver, action);
return;
}
_actions[receiver] = action;
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary>
/// Adds <paramref name="receiver"/> to the list of subscribers for this event.<br/>
/// When the <see cref="RaiseEvent"/> method is called, any subscribed <paramref name="action"/>s will be invoked.<br/>
/// </summary>
/// <remarks>
/// If the event has already been raised at least once, the <paramref name="action"/> will automatically be invoked.<br/>
/// This is useful for synchronization, such as state data which may not be available until the event has been raised at least once.<br/>
/// Using the <see cref="SubscribeAndDo"/> method we can subscribe to be notified as soon as state data is available,<br/>
/// and invoke the <paramref name="action"/> as soon as it is, invoking immediately if it already available.
/// </remarks>
public async Task SubscribeAndDo(object receiver, Func<T, Task> action)
{
await _semaphoreSlim.WaitAsync();
try
{
if (receiver == null || action == null) return;
if (!_actions.ContainsKey(receiver))
{
_actions.Add(receiver, action);
if (!Fired) return;
await action.Invoke(LastValue);
}
_actions[receiver] = action;
if (!Fired) return;
await action.Invoke(LastValue);
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary>
/// Adds <paramref name="receiver"/> to the list of subscribers for this event.<br/>
/// When the <see cref="RaiseEvent"/> method is called, any subscribed <paramref name="action"/>s will be invoked.<br/>
/// After the <see cref="RaiseEvent"/> method is called, this <paramref name="action"/> will be unsubscribed automatically.<br/>
/// </summary>
/// <remarks>
/// If the event has already been raised at least once, the <paramref name="action"/> will automatically be invoked.<br/>
/// This is useful for synchronization, such as state data which may not be available until the event has been raised at least once.<br/>
/// Using the <see cref="SubscribeAndDo"/> method we can subscribe to be notified as soon as state data is available,<br/>
/// and invoke the <paramref name="action"/> as soon as it is, invoking immediately if it already available.<br/>
/// Using the <see cref="SubscribeAndDoOnce"/> method we can subscribe just like <br/>
/// using the <see cref="SubscribeAndDo"/> method, but our action will also be automatically unsubscribed once it is called.
/// </remarks>
public async Task SubscribeAndDoOnce(object receiver, Func<T, Task> action)
{
await _semaphoreSlim.WaitAsync();
try
{
if (receiver == null || action == null) return;
if (Fired)
{
await action.Invoke(LastValue);
return;
}
if (!_oneTimeActions.Contains(receiver))
_oneTimeActions.Add(receiver);
if (!_actions.ContainsKey(receiver))
{
_actions.Add(receiver, action);
return;
}
_actions[receiver] = action;
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary> Removes <paramref name="receiver"/> from the list of subscribers for this event. </summary>
public async Task Unsubscribe(object receiver)
{
await _semaphoreSlim.WaitAsync();
try
{
if (!_actions.ContainsKey(receiver)) return;
_actions.Remove(receiver);
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary> Raises this event, invoking any actions for receivers which are currently subscribed to this event. </summary>
public async Task RaiseEvent(T value)
{
await _semaphoreSlim.WaitAsync();
try
{
Fired = true;
LastValue = value;
foreach (var action in _actions.Where(action => action.Value != null))
{
await action.Value.Invoke(LastValue);
}
foreach (var receiver in _oneTimeActions.Where(receiver => _actions.ContainsKey(receiver)))
{
_actions.Remove(receiver);
}
_oneTimeActions.Clear();
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary> Resets this event back to the default unfired state, setting the <see cref="Fired"/> property back to false. </summary>
public async Task Reset()
{
await _semaphoreSlim.WaitAsync();
try
{
Fired = false;
LastValue = default;
}
finally
{
_semaphoreSlim.Release();
}
}
}
public class Event<T1, T2>
{
/// <summary> Indicates whether this event has been fired at least one time since it was last <see cref="Reset"/>. </summary>
public bool Fired { get; private set; }
public T1 LastValue1 { get; private set; }
public T2 LastValue2 { get; private set; }
private readonly SemaphoreSlim _semaphoreSlim = new(1, 1);
private readonly List<object> _oneTimeActions = new();
private readonly Dictionary<object, Func<T1, T2, Task>> _actions = new();
/// <summary>
/// Adds <paramref name="receiver"/> to the list of subscribers for this event.<br/>
/// When the <see cref="RaiseEvent"/> method is called, any subscribed <paramref name="action"/>s will be invoked.<br/>
/// </summary>
public async Task Subscribe(object receiver, Func<T1, T2, Task> action)
{
await _semaphoreSlim.WaitAsync();
try
{
if (receiver == null || action == null) return;
if (!_actions.ContainsKey(receiver))
{
_actions.Add(receiver, action);
return;
}
_actions[receiver] = action;
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary>
/// Adds <paramref name="receiver"/> to the list of subscribers for this event.<br/>
/// When the <see cref="RaiseEvent"/> method is called, any subscribed <paramref name="action"/>s will be invoked.<br/>
/// </summary>
/// <remarks>
/// If the event has already been raised at least once, the <paramref name="action"/> will automatically be invoked.<br/>
/// This is useful for synchronization, such as state data which may not be available until the event has been raised at least once.<br/>
/// Using the <see cref="SubscribeAndDo"/> method we can subscribe to be notified as soon as state data is available,<br/>
/// and invoke the <paramref name="action"/> as soon as it is, invoking immediately if it already available.
/// </remarks>
public async Task SubscribeAndDo(object receiver, Func<T1, T2, Task> action)
{
await _semaphoreSlim.WaitAsync();
try
{
if (receiver == null || action == null) return;
if (!_actions.ContainsKey(receiver))
{
_actions.Add(receiver, action);
if (!Fired) return;
await action.Invoke(LastValue1, LastValue2);
}
_actions[receiver] = action;
if (!Fired) return;
await action.Invoke(LastValue1, LastValue2);
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary>
/// Adds <paramref name="receiver"/> to the list of subscribers for this event.<br/>
/// When the <see cref="RaiseEvent"/> method is called, any subscribed <paramref name="action"/>s will be invoked.<br/>
/// After the <see cref="RaiseEvent"/> method is called, this <paramref name="action"/> will be unsubscribed automatically.<br/>
/// </summary>
/// <remarks>
/// If the event has already been raised at least once, the <paramref name="action"/> will automatically be invoked.<br/>
/// This is useful for synchronization, such as state data which may not be available until the event has been raised at least once.<br/>
/// Using the <see cref="SubscribeAndDo"/> method we can subscribe to be notified as soon as state data is available,<br/>
/// and invoke the <paramref name="action"/> as soon as it is, invoking immediately if it already available.<br/>
/// Using the <see cref="SubscribeAndDoOnce"/> method we can subscribe just like <br/>
/// using the <see cref="SubscribeAndDo"/> method, but our action will also be automatically unsubscribed once it is called.
/// </remarks>
public async Task SubscribeAndDoOnce(object receiver, Func<T1, T2, Task> action)
{
await _semaphoreSlim.WaitAsync();
try
{
if (receiver == null || action == null) return;
if (Fired)
{
await action.Invoke(LastValue1, LastValue2);
return;
}
if (!_oneTimeActions.Contains(receiver))
_oneTimeActions.Add(receiver);
if (!_actions.ContainsKey(receiver))
{
_actions.Add(receiver, action);
return;
}
_actions[receiver] = action;
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary> Removes <paramref name="receiver"/> from the list of subscribers for this event. </summary>
public async Task Unsubscribe(object receiver)
{
await _semaphoreSlim.WaitAsync();
try
{
if (!_actions.ContainsKey(receiver)) return;
_actions.Remove(receiver);
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary> Raises this event, invoking any actions for receivers which are currently subscribed to this event. </summary>
public async Task RaiseEvent(T1 value1, T2 value2)
{
await _semaphoreSlim.WaitAsync();
try
{
Fired = true;
LastValue1 = value1;
LastValue2 = value2;
foreach (var action in _actions.Where(action => action.Value != null))
{
await action.Value.Invoke(LastValue1, LastValue2);
}
foreach (var receiver in _oneTimeActions.Where(receiver => _actions.ContainsKey(receiver)))
{
_actions.Remove(receiver);
}
_oneTimeActions.Clear();
}
finally
{
_semaphoreSlim.Release();
}
}
/// <summary> Resets this event back to the default unfired state, setting the <see cref="Fired"/> property back to false. </summary>
public async Task Reset()
{
await _semaphoreSlim.WaitAsync();
try
{
Fired = false;
LastValue1 = default;
LastValue2 = default;
}
finally
{
_semaphoreSlim.Release();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment