Skip to content

Instantly share code, notes, and snippets.

@codemaster
Created October 8, 2017 18:35
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 codemaster/98d4fc773912a899752e42a2d4f71116 to your computer and use it in GitHub Desktop.
Save codemaster/98d4fc773912a899752e42a2d4f71116 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
#if NET_4_6
using System.Threading.Tasks;
#endif
/// <summary>
/// Event manager
/// </summary>
public class EventManager : IEventManager
{
/// <summary>
/// Handlers - type to hashset of actions
/// </summary>
private readonly Dictionary<Type, object> _handlers
= new Dictionary<Type, object>();
/// <summary>
/// Adds a handler
/// </summary>
/// <returns><c>true</c>, if handler was added, <c>false</c> otherwise.</returns>
/// <param name="handler">The handler.</param>
/// <typeparam name="T">The type that this handler is listening for</typeparam>
public bool AddHandler<T>(Action<T> handler)
{
if (null == handler)
{
throw new ArgumentNullException(nameof(handler));
}
var handlerSet = GetOrCreateHandlerSet<T>();
return handlerSet.Add(handler);
}
/// <summary>
/// Removes a handler.
/// </summary>
/// <returns><c>true</c>, if handler was removed, <c>false</c> otherwise.</returns>
/// <param name="handler">The handler.</param>
/// <typeparam name="T">The type that this handler will stop listening for</typeparam>
public bool RemoveHandler<T>(Action<T> handler)
{
if (null == handler)
{
throw new ArgumentNullException(nameof(handler));
}
var handlerSet = GetOrCreateHandlerSet<T>();
return handlerSet.Remove(handler);
}
/// <summary>
/// Dispatches a message to any handlers that need it
/// </summary>
/// <param name="arg">The message itself.</param>
/// <typeparam name="T">The message being sent to any handlers that need it.</typeparam>
public void Dispatch<T>(T arg)
{
var handlerSet = GetOrCreateHandlerSet<T>();
foreach (var handler in handlerSet)
{
handler.Invoke(arg);
}
}
#if NET_4_6
/// <summary>
/// Dispatches a message to any handles that need it in parallel
/// </summary>
/// <param name="arg">The message itself.</param>
/// <typeparam name="T">The message being sent to any hanadlers that need it.</typeparam>
public void DispatchParallel<T>(T arg)
{
var handlerSet = GetOrCreateHandlerSet<T>();
var tasks = new Task[handlerSet.Count];
var index = 0;
foreach (var handler in handlerSet)
{
tasks[index++] = Task.Run(() => handler.Invoke(arg));
}
Task.WaitAll(tasks);
}
#endif
/// <summary>
/// Resets the handlers for a specific message type
/// </summary>
/// <typeparam name="T">The message type to reset handlers for.</typeparam>
public void Reset<T>()
{
var hashSet = GetOrCreateHandlerSet<T>();
hashSet.Clear();
}
/// <summary>
/// Resets all handlers
/// </summary>
public void ResetAll()
{
_handlers.Clear();
}
/// <summary>
/// Gets the creates handler hash set in the dictionary.
/// </summary>
/// <returns>The handler set that was found or created.</returns>
/// <typeparam name="T">The type that the handler set is waiting for.</typeparam>
private HashSet<Action<T>> GetOrCreateHandlerSet<T>()
{
var type = typeof(T);
HashSet<Action<T>> handlerSet;
object absHandler;
if (!_handlers.TryGetValue(type, out absHandler))
{
handlerSet = new HashSet<Action<T>>();
_handlers[type] = handlerSet;
} else {
handlerSet = absHandler as HashSet<Action<T>>;
}
return handlerSet;
}
}
using NUnit.Framework;
using System;
public class EventManagerTests
{
private readonly IEventManager _eventManager = new EventManager();
[SetUp]
public void SetUp()
{
_eventManager.ResetAll();
}
[Test]
public void AddHandler_ShouldSucceed_WhenAddedOnce()
{
// Arrange
Action<string> handler = (str) => { };
// Act
var added = _eventManager.AddHandler(handler);
// Assert
Assert.IsTrue(added);
}
[Test]
public void AddHandler_ShouldFail_WhenAddedMoreThanOnce()
{
// Arrange
Action<string> handler = (str) => { };
// Act
var added1 = _eventManager.AddHandler(handler);
var added2 = _eventManager.AddHandler(handler);
// Assert
Assert.IsTrue(added1);
Assert.IsFalse(added2);
}
[Test]
public void RemoveHandler_ShouldSucceed_WhenRemovingAddedHandler()
{
// Arrange
Action<string> handler = (str) => { };
// Act
_eventManager.AddHandler(handler);
var removed = _eventManager.RemoveHandler(handler);
// Assert
Assert.IsTrue(removed);
}
[Test]
public void RemoveHandler_ShouldFail_WhenRemovingUnaddedHandler()
{
// Arrange
Action<string> handler = (str) => { };
// Act
var removed = _eventManager.RemoveHandler(handler);
// Assert
Assert.IsFalse(removed);
}
[Test]
public void Dispatch_ShouldKickOffHandler_WhenDispatching()
{
// Arrange
var success = false;
Action<string> handler = (str) => { success = true; };
// Act
_eventManager.AddHandler(handler);
_eventManager.Dispatch("");
// Assert
Assert.IsTrue(success);
}
[Test]
public void Dispatch_ShouldNotKickOffHandler_WhenDispatchingDifferentType()
{
// Arrange
var success = false;
Action<string> handler = (str) => { success = true; };
// Act
_eventManager.AddHandler(handler);
_eventManager.Dispatch(true);
// Assert
Assert.IsFalse(success);
}
#if NET_4_6
[Test]
public void DispatchParallel_ShouldKickOffHandler_WhenDispatching()
{
// Arrange
var success = false;
Action<string> handler = (str) => { success = true; };
// Act
_eventManager.AddHandler(handler);
_eventManager.DispatchParallel("");
// Assert
Assert.IsTrue(success);
}
[Test]
public void DispatchParallel_ShouldNotKickOffHandler_WhenDispatchingDifferentType()
{
// Arrange
var success = false;
Action<string> handler = (str) => { success = true; };
// Act
_eventManager.AddHandler(handler);
_eventManager.DispatchParallel(true);
// Assert
Assert.IsFalse(success);
}
#endif
[Test]
public void Reset_ShouldResetOnlySpecificHandler_WhenResetting()
{
// Arrange
var called = false;
Action<string> handler = (str) => { called = true; };
// Act
_eventManager.AddHandler(handler);
_eventManager.Reset<string>();
_eventManager.Dispatch("");
// Assert
Assert.IsFalse(called);
}
[Test]
public void ResetAll_ShouldResetAllHandlers_WhenResetting()
{
// Arrange
var called = false;
Action<string> handler1 = (str) => { called = true; };
Action<bool> handler2 = (val) => { called = true; };
// Act
_eventManager.AddHandler(handler1);
_eventManager.AddHandler(handler2);
_eventManager.ResetAll();
_eventManager.Dispatch("");
_eventManager.Dispatch(true);
// Assert
Assert.IsFalse(called);
}
}
using System;
/// <summary>
/// Event manager interface
/// </summary>
public interface IEventManager
{
/// <summary>
/// Adds a handler
/// </summary>
/// <returns><c>true</c>, if handler was added, <c>false</c> otherwise.</returns>
/// <param name="handler">The handler.</param>
/// <typeparam name="T">The type that this handler is listening for</typeparam>
bool AddHandler<T>(Action<T> handler);
/// <summary>
/// Removes a handler.
/// </summary>
/// <returns><c>true</c>, if handler was removed, <c>false</c> otherwise.</returns>
/// <param name="handler">The handler.</param>
/// <typeparam name="T">The type that this handler will stop listening for</typeparam>
bool RemoveHandler<T>(Action<T> handler);
/// <summary>
/// Dispatches a message to any handlers that need it
/// </summary>
/// <param name="arg">The message itself.</param>
/// <typeparam name="T">The message being sent to any handlers that need it.</typeparam>
void Dispatch<T>(T arg);
#if NET_4_6
/// <summary>
/// Dispatches a message to any handles that need it in parallel
/// </summary>
/// <param name="arg">The message itself.</param>
/// <typeparam name="T">The message being sent to any hanadlers that need it.</typeparam>
void DispatchParallel<T>(T arg);
#endif
/// <summary>
/// Resets the handlers for a specific message type
/// </summary>
/// <typeparam name="T">The message type to reset handlers for.</typeparam>
void Reset<T>();
/// <summary>
/// Resets all handlers
/// </summary>
void ResetAll();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment