namespace Easy.MessageHub
{
using System;
using System.Diagnostics;
#if NETSTANDARD
using System.Reflection;
#endif
///
/// An implementation of the Event Aggregator pattern.
///
public sealed class MessageHub : IMessageHub
{
private readonly Subscriptions _subscriptions;
private Action _globalHandler;
private Action _globalErrorHandler;
///
/// Creates an instance of the .
///
public MessageHub() => _subscriptions = new Subscriptions();
///
/// Registers a callback which is invoked on every message published by the .
/// Invoking this method with a new overwrites the previous one.
///
///
/// The callback to invoke on every message
/// The callback receives the type of the message and the message as arguments
///
public void RegisterGlobalHandler(Action onMessage)
{
EnsureNotNull(onMessage);
_globalHandler = onMessage;
}
///
/// Invoked if an error occurs when publishing a message to a subscriber.
/// Invoking this method with a new overwrites the previous one.
///
public void RegisterGlobalErrorHandler(Action onError)
{
EnsureNotNull(onError);
_globalErrorHandler = onError;
}
///
/// Publishes the on the .
///
/// The message to published
public void Publish(T message)
{
var localSubscriptions = _subscriptions.GetTheLatestSubscriptions();
var msgType = typeof(T);
#if NETSTANDARD
var msgTypeInfo = msgType.GetTypeInfo();
#endif
_globalHandler?.Invoke(msgType, message);
// ReSharper disable once ForCanBeConvertedToForeach | Performance Critical
for (var idx = 0; idx < localSubscriptions.Count; idx++)
{
var subscription = localSubscriptions[idx];
#if NETSTANDARD
if (!subscription.Type.GetTypeInfo().IsAssignableFrom(msgTypeInfo)) { continue; }
#else
if (!subscription.Type.IsAssignableFrom(msgType)) { continue; }
#endif
try
{
subscription.Handle(message);
}
catch (Exception e)
{
_globalErrorHandler?.Invoke(subscription.Token, e);
}
}
}
///
/// Subscribes a callback against the for a specific type of message.
///
/// The type of message to subscribe to
/// The callback to be invoked once the message is published on the
/// The token representing the subscription
public Guid Subscribe(Action action) => Subscribe(action, TimeSpan.Zero);
///
/// Subscribes a callback against the for a specific type of message.
///
/// The type of message to subscribe to
/// The callback to be invoked once the message is published on the
/// The specifying the rate at which subscription is throttled
/// The token representing the subscription
public Guid Subscribe(Action action, TimeSpan throttleBy)
{
EnsureNotNull(action);
return _subscriptions.Register(throttleBy, action);
}
///
/// Unsubscribes a subscription from the .
///
/// The token representing the subscription
public void Unsubscribe(Guid token) => _subscriptions.UnRegister(token);
///
/// Checks if a specific subscription is active on the .
///
/// The token representing the subscription
/// True if the subscription is active otherwise False
public bool IsSubscribed(Guid token) => _subscriptions.IsRegistered(token);
///
/// Clears all the subscriptions from the .
/// The global handler and the global error handler are not affected
///
public void ClearSubscriptions() => _subscriptions.Clear(false);
///
/// Disposes the .
///
public void Dispose()
{
_globalHandler = null;
_globalErrorHandler = null;
_subscriptions.Clear(true);
}
[DebuggerStepThrough]
private void EnsureNotNull(object obj)
{
if (obj is null) { throw new NullReferenceException(nameof(obj)); }
}
}
}