c# - 使用简单注入(inject)器在 C# 中实现域事件处理程序模式

标签 c# generics inversion-of-control simple-injector

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

我已将我的代码简化为一个可以作为控制台应用程序运行的文件,并排除了 Simple Injector 代码以明确此问题的目的。

我遇到的问题是每个事件都可以有多个事件处理程序并且可以引发多个事件但我想限制我的 Dispatcher 只处理实现 IEvent 接口(interface)的事件所以我将这种限制放在我的 Dispatch 方法上。

这导致了关于如何从 Simple Injector 获取实例的问题,因为每次调用 Dispatch 方法时 TEvent 都是 IEvent 类型(如我所料)但我需要获取传入的事件类型,以便我可以从 Simple Injector 获取相关的处理程序。

希望我的代码能更好地解释这一点:

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

这是我的调度员:

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

我已经在 Dispatch 方法中评论了我的问题所在。有谁知道我应该如何解决这个问题?

问候, 加里

最佳答案

您需要的解决方案有点取决于 Dispatcher 的消费者如何使用调用事件。如果消费者在编译时总是知道事件的确切类型,则可以使用通用 Dispatch<TEvent>(TEvent)方法如上所示。在这种情况下 Dispatcher的实现将非常简单。

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

另请注意,最好引入一个 IEventDispatcher抽象。不要从代码中调用静态类。甚至 Udi Dahan(很久以前最初是 described such static class 的人)现在也认为这是一种反模式。相反,注入(inject) 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);
        }
    }
}

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

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

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

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

container.Collection.Register(typeof(IEventHandler<>), listOfAssembliesToSearch);

关于c# - 使用简单注入(inject)器在 C# 中实现域事件处理程序模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30625363/

相关文章:

inversion-of-control - kernel.Bind<SomeType>().ToSelf() 做什么?

c# - 如何从 Web 服务获取 XML?

c# - 什么会导致我的 SQL Server 权限从存储过程的一次执行更改为下一次执行?

c# - 为通用类型创建 Moq 实例

c# - 多线程for循环同时保持顺序

c# - C#泛型类型约束-防止类型参数相同?

generics - 从 new 返回通用结构

generics - Elixir:定义映射/哈希字典的类型规范

java - AWS Java SDK - 什么更快? DynamoDB 客户端 (@ApplicationScoped) 的单个实例还是为每个请求创建一个新实例?

.net - 温莎城堡问题