mirror of https://github.com/AMT-Cheif/Stylet.git
Finish EventAggregator, and start writing tests
This commit is contained in:
parent
08d80404ec
commit
f41645ab89
|
@ -2,6 +2,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@ -16,52 +17,124 @@ namespace Stylet
|
||||||
void Handle(TMessageType message);
|
void Handle(TMessageType message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class EventAggregator
|
public interface IEventAggregator
|
||||||
|
{
|
||||||
|
void Subscribe(IHandle handler);
|
||||||
|
void Unsubscribe(IHandle handler);
|
||||||
|
|
||||||
|
void PublishWithDispatcher(object message, Action<Action> dispatcher);
|
||||||
|
void PublishOnUIThread(object message);
|
||||||
|
void Publish(object message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EventAggregator : IEventAggregator
|
||||||
{
|
{
|
||||||
private readonly List<Handler> handlers = new List<Handler>();
|
private readonly List<Handler> handlers = new List<Handler>();
|
||||||
|
private readonly object handlersLock = new object();
|
||||||
|
|
||||||
public void Subscribe(IHandle handler)
|
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<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)
|
public void Publish(object message)
|
||||||
{
|
{
|
||||||
|
this.PublishWithDispatcher(message, x => x());
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Handler
|
private class Handler
|
||||||
{
|
{
|
||||||
private readonly WeakReference target;
|
private readonly WeakReference target;
|
||||||
private readonly Dictionary<Type, Action<object>> handlers = new Dictionary<Type, Action<object>>();
|
private readonly List<HandlerInvoker> invokers = new List<HandlerInvoker>();
|
||||||
|
|
||||||
public Handler(object handler)
|
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 messageType = implementation.GetGenericArguments()[0];
|
||||||
var method = type.GetMethod("Handle");
|
this.invokers.Add(new HandlerInvoker(handlerType, messageType, implementation.GetMethod("Handle")));
|
||||||
var param = Expression.Parameter(type, "message");
|
|
||||||
var caller = Expression.Lambda<Action<object>>(Expression.Call(method, param), param).Compile();
|
|
||||||
this.handlers.Add(type, caller);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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<Action> dispatcher)
|
||||||
{
|
{
|
||||||
var target = this.target.Target;
|
var target = this.target.Target;
|
||||||
if (target == null)
|
if (target == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
foreach (var invoker in this.invokers)
|
||||||
|
{
|
||||||
|
invoker.Invoke(target, messageType, message, dispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class HandlerInvoker
|
||||||
|
{
|
||||||
|
private readonly Type messageType;
|
||||||
|
private readonly Action<object, object> 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<Action<object, object>>(callExpression, targetParam, messageParam).Compile();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Invoke(object target, Type messageType, object message, Action<Action> dispatcher)
|
||||||
|
{
|
||||||
|
if (this.messageType.IsAssignableFrom(messageType))
|
||||||
|
dispatcher(() => this.invoker(target, message));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<M1>
|
||||||
|
{
|
||||||
|
public M1 ReceivedMessage;
|
||||||
|
public void Handle(M1 message) { this.ReceivedMessage = message; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class C2 : IHandle<M2>, IHandle<M1>
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,6 +45,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="BindableCollectionTests.cs" />
|
<Compile Include="BindableCollectionTests.cs" />
|
||||||
|
<Compile Include="EventAggregatorTests.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="StyletIoC\StyletIoCAutobindingTests.cs" />
|
<Compile Include="StyletIoC\StyletIoCAutobindingTests.cs" />
|
||||||
<Compile Include="StyletIoC\StyletIoCBindingChecksTests.cs" />
|
<Compile Include="StyletIoC\StyletIoCBindingChecksTests.cs" />
|
||||||
|
|
Loading…
Reference in New Issue