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