Skip to content

Instantly share code, notes, and snippets.

@rquackenbush
Created July 28, 2025 22:54
Show Gist options
  • Select an option

  • Save rquackenbush/796ccf839e58a0bb11c0ca4ea937a049 to your computer and use it in GitHub Desktop.

Select an option

Save rquackenbush/796ccf839e58a0bb11c0ca4ea937a049 to your computer and use it in GitHub Desktop.
Simple notification framework to let interested parties know that something has happened in a decoupled way.
using System.Collections.Concurrent;
namespace TheNotifer;
internal class NotificationSource : IDisposable
{
private bool _disposed;
private readonly ConcurrentDictionary<IDisposable, Subscription> _notifications = new ConcurrentDictionary<IDisposable, Subscription>();
public void Notify()
{
foreach (var subscription in _notifications.Values.ToArray())
{
try
{
subscription.Notify();
}
catch (Exception)
{
}
}
}
public IDisposable Subscribe(Action action)
{
if (action is null)
throw new ArgumentNullException(nameof(action));
var subscription = new Subscription(this, action);
if (!_notifications.TryAdd(subscription, subscription))
throw new InvalidOperationException("Unable to add subscription. Weird.");
return subscription;
}
internal void Unsubscribe(IDisposable subscription)
{
_notifications.TryRemove(subscription, out _);
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
GC.SuppressFinalize(this);
}
}
}
internal class Subscription : IDisposable
{
private bool _disposed;
private readonly NotificationSource _owner;
private readonly Action _action;
public Subscription(NotificationSource owner, Action action)
{
_owner = owner ?? throw new ArgumentNullException(nameof(owner));
_action = action ?? throw new ArgumentNullException(nameof(_action));
}
internal void Notify()
{
if (!_disposed)
{
_action.Invoke();
}
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
_owner.Unsubscribe(this);
GC.SuppressFinalize(this);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment