From f41645ab8989157b7a68e1c00f0c697b20e2689a Mon Sep 17 00:00:00 2001 From: Antony Male Date: Tue, 25 Feb 2014 13:12:34 +0000 Subject: [PATCH] Finish EventAggregator, and start writing tests --- Stylet/EventAggregator.cs | 99 +++++++++++++++++++++---- StyletUnitTests/EventAggregatorTests.cs | 58 +++++++++++++++ StyletUnitTests/StyletUnitTests.csproj | 1 + 3 files changed, 145 insertions(+), 13 deletions(-) create mode 100644 StyletUnitTests/EventAggregatorTests.cs diff --git a/Stylet/EventAggregator.cs b/Stylet/EventAggregator.cs index 42c7eff..8511c8f 100644 --- a/Stylet/EventAggregator.cs +++ b/Stylet/EventAggregator.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -16,52 +17,124 @@ namespace Stylet void Handle(TMessageType message); } - public class EventAggregator + public interface IEventAggregator + { + void Subscribe(IHandle handler); + void Unsubscribe(IHandle handler); + + void PublishWithDispatcher(object message, Action dispatcher); + void PublishOnUIThread(object message); + void Publish(object message); + } + + public class EventAggregator : IEventAggregator { private readonly List handlers = new List(); + private readonly object handlersLock = new object(); public void Subscribe(IHandle handler) { + lock (this.handlersLock) + { + // Is it already subscribed? + if (this.handlers.Any(x => x.IsHandlerForInstance(handler))) + return; + this.handlers.Add(new Handler(handler)); + } + } + + public void Unsubscribe(IHandle handler) + { + lock (this.handlersLock) + { + var existingHandler = this.handlers.FirstOrDefault(x => x.IsHandlerForInstance(handler)); + if (existingHandler != null) + this.handlers.Remove(existingHandler); + } + } + + public void PublishWithDispatcher(object message, Action dispatcher) + { + lock (this.handlersLock) + { + var messageType = message.GetType(); + var deadHandlers = this.handlers.Where(x => !x.Handle(messageType, message, dispatcher)).ToArray(); + foreach (var deadHandler in deadHandlers) + { + this.handlers.Remove(deadHandler); + } + } + } + + public void PublishOnUIThread(object message) + { + this.PublishWithDispatcher(message, Execute.OnUIThread); } public void Publish(object message) { - + this.PublishWithDispatcher(message, x => x()); } private class Handler { private readonly WeakReference target; - private readonly Dictionary> handlers = new Dictionary>(); + private readonly List invokers = new List(); public Handler(object handler) { - this.target = new WeakReference(target); + var handlerType = handler.GetType(); + this.target = new WeakReference(handler); - foreach (var implementation in this.target.GetType().GetInterfaces().Where(x => x.IsGenericType && typeof(IHandle).IsAssignableFrom(x))) + foreach (var implementation in handler.GetType().GetInterfaces().Where(x => x.IsGenericType && typeof(IHandle).IsAssignableFrom(x))) { - var type = implementation.GetGenericArguments()[0]; - var method = type.GetMethod("Handle"); - var param = Expression.Parameter(type, "message"); - var caller = Expression.Lambda>(Expression.Call(method, param), param).Compile(); - this.handlers.Add(type, caller); + var messageType = implementation.GetGenericArguments()[0]; + this.invokers.Add(new HandlerInvoker(handlerType, messageType, implementation.GetMethod("Handle"))); } } - public bool IsOfType(object subscriberType) + public bool IsHandlerForInstance(object subscriber) { - return this.target.Target == subscriberType; + return this.target.Target == subscriber; } - public bool Handle(Type messageType, object message) + public bool Handle(Type messageType, object message, Action dispatcher) { var target = this.target.Target; if (target == null) return false; + foreach (var invoker in this.invokers) + { + invoker.Invoke(target, messageType, message, dispatcher); + } + return true; } } + + private class HandlerInvoker + { + private readonly Type messageType; + private readonly Action invoker; + + public HandlerInvoker(Type targetType, Type messageType, MethodInfo invocationMethod) + { + this.messageType = messageType; + var targetParam = Expression.Parameter(typeof(object), "target"); + var messageParam = Expression.Parameter(typeof(object), "message"); + var castTarget = Expression.Convert(targetParam, targetType); + var castMessage = Expression.Convert(messageParam, messageType); + var callExpression = Expression.Call(castTarget, invocationMethod, castMessage); + this.invoker = Expression.Lambda>(callExpression, targetParam, messageParam).Compile(); + } + + public void Invoke(object target, Type messageType, object message, Action dispatcher) + { + if (this.messageType.IsAssignableFrom(messageType)) + dispatcher(() => this.invoker(target, message)); + } + } } } diff --git a/StyletUnitTests/EventAggregatorTests.cs b/StyletUnitTests/EventAggregatorTests.cs new file mode 100644 index 0000000..ab34545 --- /dev/null +++ b/StyletUnitTests/EventAggregatorTests.cs @@ -0,0 +1,58 @@ +using NUnit.Framework; +using Stylet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StyletUnitTests +{ + [TestFixture] + public class EventAggregatorTests + { + public class M1 { } + public class M2 : M1 { } + + public class C1 : IHandle + { + public M1 ReceivedMessage; + public void Handle(M1 message) { this.ReceivedMessage = message; } + } + + public class C2 : IHandle, IHandle + { + public M2 ReceivedM2; + public M1 ReceivedM1; + public void Handle(M2 message) { this.ReceivedM2 = message; } + public void Handle(M1 message) { this.ReceivedM1 = message; } + } + + [Test] + public void SubscribesAndDeliversExactMessage() + { + var ea = new EventAggregator(); + var target = new C1(); + ea.Subscribe(target); + + var message = new M1(); + ea.Publish(message); + + Assert.AreEqual(message, target.ReceivedMessage); + } + + [Test] + public void DeliversToAllHandlersIncludingDerived() + { + var ea = new EventAggregator(); + var target = new C2(); + ea.Subscribe(target); + + var message = new M2(); + ea.Publish(message); + + Assert.AreEqual(message, target.ReceivedM1); + Assert.AreEqual(message, target.ReceivedM2); + } + } +} diff --git a/StyletUnitTests/StyletUnitTests.csproj b/StyletUnitTests/StyletUnitTests.csproj index 226613d..5af1b65 100644 --- a/StyletUnitTests/StyletUnitTests.csproj +++ b/StyletUnitTests/StyletUnitTests.csproj @@ -45,6 +45,7 @@ +