使用Simple Injector在C#中实现域事件处理程序模式

Gar*_*y F 4 c# generics inversion-of-control simple-injector

我正在尝试使用Simple Injector在C#中实现域事件模式.

我已经将我的代码简化为一个文件,该文件可以作为控制台应用程序运行,并且已经排除了Simple Injector代码,以便为此问题保持清晰.

我遇到的问题是每个事件可能有多个事件处理程序,并且可能会引发多个事件,但我想限制我的Dispatcher只处理实现该IEvent接口的事件,所以我把这个限制放在我的Dispatch方法上.

这引起了如何从Simple Injector获取实例的问题,因为每次Dispatch调用该方法都是TEvent类型IEvent(正如我所期望的那样)但是我需要获取传入的事件的类型,以便我可以从Simple获取相关的处理程序注射器.

希望我的代码能够更好地解释这个:

interface IEvent 
{
}

interface IEventHandler<T> where T : IEvent
{
    void Handle(T @event);
}

class StandardEvent : IEvent
{
}

class AnotherEvent : IEvent
{
}

class StandardEventHandler : IEventHandler<StandardEvent>
{
    public void Handle(StandardEvent @event)
    {
        Console.WriteLine("StandardEvent handled");
    }
}

class AnotherEventHandler : IEventHandler<AnotherEvent>
{
    public void Handle(AnotherEvent @event)
    {
        Console.WriteLine("AnotherEvent handled");
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的调度员:

static class Dispatcher
{
    // I need to get the type of @event here so I can get the registered instance from the
    // IoC container (SimpleInjector), however TEvent is of type IEvent (as expected). 
    // What I need to do here is Get the registered instance from Simple Injector for each
    // Event Type i.e. Container.GetAllInstances<IEventHandler<StandardEvent>>()
    // and Container.GetAllInstances<IEventHandler<AnotherEvent>>()
    public static void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent
    {
    }
}

class PlainOldObject
{
    public ICollection<IEvent> Events = new List<IEvent>
    {
        new StandardEvent(),
        new AnotherEvent()
    };
}

class StandAlone
{
    static void Main(string[] args)
    {
        var poco = new PlainOldObject();
        foreach (var @event in poco.Events)
        {
            Dispatcher.Dispatch(@event);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我在Dispatch方法中评论了我的问题.有没有人知道如何解决这个问题?

此致,加里

Ste*_*ven 9

您需要的解决方案有点依赖于Dispatcher调用事件的消费者.如果使用者在编译时始终知道事件的确切类型,则可以使用Dispatch<TEvent>(TEvent)上面显示的泛型方法.在这种情况下,Dispatcher实施将非常简单.

另一方面,如果消费者可能并不总是知道确切的类型,而只是简单地使用IEvent接口,泛型类型参数Dispatch<TEvent>(TEvent)变得毫无用处,最好定义一个Dispatch(IEvent)方法.这使得实现更复杂一些,因为您需要使用反射来解决这个问题.

还要注意引入IEventDispatcher抽象会很好.不要在代码中调用静态类.甚至Udi Dahan(很久以前最初描述过这种静态类)现在也认为这是一种反模式.相反,将IEventDispatcher抽象注入需要事件调度的类中.

如果所有使用者都使用编译时已知的事件类型,您的实现将如下所示:

public interface IEventDispatcher
{
    void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent;
}

private sealed class Dispatcher : IEventDispatcher
{
    private readonly Container container;
    public Dispatcher(Container container) {
        this.container = container;
    }

    public void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent {
        if (@event == null) throw new ArgumentNullException("event");

        var handlers = this.container.GetAllInstances<IEventHandler<TEvent>>();

        foreach (var handler in handlers) {
            handler.Handle(@event);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

另一方面,如果事件类型未知,则可以使用以下代码:

public interface IEventDispatcher
{
    void Dispatch(IEvent @event);
}

private sealed class Dispatcher : IEventDispatcher
{
    private readonly Container container;
    public Dispatcher(Container container) {
        this.container = container;
    }

    public void Dispatch(IEvent @event) {
        if (@event == null) throw new ArgumentNullException("event");

        Type handlerType = typeof(IEventHandler<>).MakeGenericType(@event.GetType());

        var handlers = this.container.GetAllInstances(handlerType);

        foreach (dynamic handler in handlers) {
            handler.Handle((dynamic)@event);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,与使用.NET反射API相比,使用dynamic关键字有一些不明显的优势.例如,当Handle使用dynamic 调用处理程序的方法时,从句柄抛出的任何异常都会直接冒泡.MethodInfo.Invoke另一方面,当使用时,异常将包含在新的异常中.这使得捕获和调试更加困难.

您的事件处理程序可以注册如下:

container.RegisterCollection(typeof(IEventHandler<>), listOfAssembliesToSearch);
Run Code Online (Sandbox Code Playgroud)