Выбрать / использовать реализацию интерфейса с универсальным параметром на основе типа другого объекта

Я работаю над системой обработки событий:

public interface IEvent { ..}
public class CreateUserEvent : IEvent {...}
public class ChangeUserNameEvent : IEvent {...}

Каждое событие имеет определенный обработчик

public interface IEventHandler<T> where T : IEvent { Handle(T @event); }
public class CreateUserEventHandler : IEventHandler<CreateUserEvent> { ... }
public class ChangeUserNameEventHandler : IEventHandler<ChangeUserNameEvent> {...}

Пока все довольно просто. Тем не менее, я хотел бы создать класс, который использует правильный обработчик событий для правильного события.

До сих пор я придумал следующий метод:

Dictionary<Type, object> EventHandlers; // stores all registered event handlers

// Note that at compile time I do not exactly know the specialization of IEvent 
// so I cannot give HandleEvent a generic type parameter :(
void HandleEvent(IEvent @event)
 {
    // inspect the type of the event handler at runtime
    // the event also needs to be dynamic. Even though we know its a
    // specialization of IEvent that is compatible with 
    // the handlers .Handle method
    var handler = EventHandlers[@event.GetType()] as dynamic;       
    hanler.Handle(@event as dynamic);
}

Это решение работает, но я должен использовать два динамических типа, это меня беспокоит. Я думаю, что, возможно, я принимаю неправильное проектное решение, но я не могу придумать никакой другой архитектуры / шаблона, чтобы избавиться от этой динамики.

Таким образом, мой вопрос сводится к следующему: как я могу выбрать и использовать правильную реализацию интерфейса с универсальным с минимальным самоанализом во время выполнения?

Примечание. Я предпочитаю решение, в котором реализации IEvent и IEventHandler полностью не знают об этом процессе.

1 ответ

Я хотел бы попробовать что-то свободно на основе Subject; и метода расширения OfType в Rx.NET. Это задержит проверку типов до последнего момента, поэтому вы можете переписать ее в решение на основе словаря. Также этот код никоим образом не является поточно-безопасным, используйте код Rx.NET в качестве справочного материала, чтобы улучшить его в случаях использования многопоточности.

Самая большая проблема этого решения заключается в том, что тип обработчика скрыт в вызове метода EventDispatcher.Dispatch. В этом вопросе вы утверждаете, что хотите, чтобы отправляемый метод не обладал общими сведениями о событии во время компиляции.

public interface IEvent
{   
}

public interface IEventHandler<TEvent> where TEvent: IEvent
{
    void Handle<TEvent>(TEvent message)
}

public class EventDispatcher
{
    private List<object> handlers = new List<object>();

    public void Dispatch<TEvent>(TEvent message)
    {
        foreach (var handler in handlers)
        {
            if (handler is IEventHandler<TEvent>)
            {
                var safeHandler = (IEventHandler<TEvent>)handler;
                safeHandler.Handle(message);
            }
        }
    }

    public IDisposable Register<TEvent>(IEventHandler<TEvent> handler)
    {
        this.handlers.Add(handler);
        return new Subscription(this, handler);
    }

    class Subscription : IDisposable
    {
        private EventDispatcher dispatcher;
        private IEventHandler<TEvent> handler;

        public Subscription(EventDispatcher dispatcher, IEventHandler<TEvent> handler)
        {
            this.dispatcher = dispatcher;
            this.handler = handler;
        }

        public void Dispose()
        {
            if (dispatcher == null)
                return;

            dispatcher.Unsubscribe(handler);
            dispatcher = null;
        }
    }

    private void Unsubscribe(IEventHandler<TEvent> handler)
    {
        this.handlers.Remove(handler);
    }
}
Другие вопросы по тегам