Можно ли использовать открытый универсальный в качестве аргумента конструктора? - PullRequest
5 голосов
/ 17 августа 2011

Я создал очень простой издатель событий, и он выглядит следующим образом.

public class EventPublisher
{
    private readonly IList<Func<IHandle>> _subscribers;

    public EventPublisher(IList<Func<IHandle>> subscribers)
    {
        _subscribers = subscribers;
    }

    public void Publish<TPayload>(TPayload payload)
        where TPayload : class
    {
        var payloadHandlers = _subscribers.OfType<Func<IHandle<TPayload>>>();

        foreach (var payloadHandler in payloadHandlers)
        {
            payloadHandler().Handle(payload);
        }
    }
}

Вот что я должен публиковать сообщения.

var subscribers = new List<Func<IHandle>> {() => new SomeHandler()};
var eventPublisher = new EventPublisher(subscribers);

eventPublisher.Publish(new SomeMessage { Text = "Some random text..." });

У меня проблема в том, что публикация сообщения не находит никаких обработчиков, которые могут обработать полезную нагрузку.Это имеет смысл, потому что я зарегистрировал своих подписчиков как Func<IHandle> вместо Func<IHandle<T>>.

Интерфейс, от которого наследуются классы моего обработчика, от Caliburn.Micro.EventAggregator и выглядит следующим образом.

public interface IHandle {}

public interface IHandle<TMessage> : IHandle {
    void Handle(TMessage message);
}

Каким должен быть тип _subscribers для обработки IHandle<>, где универсальным типом может быть любой конкретный тип?

Ответы [ 2 ]

3 голосов
/ 17 августа 2011

Предполагая, что вы используете .NET 4, я ожидаю, что это сработает:

var subscribers = new List<Func<IHandle<SomeMessage>>> {() => new SomeHandler()};

, а затем получит ковариацию:

public EventPublisher(IEnumerable<Func<IHandle>> subscribers)
{
    _subscribers = subscribers.ToList();
}

Это позволит вам подписаться на любую последовательностьвещей, совместимых с Func<IHandle>.Обратите внимание, что значительно меняет семантику , так как набор подписчиков будет зафиксирован после построения.Лично я бы расценил это как хорошую вещь, но она может вас не устраивать.

Или же самому EventPublisher приходится работать с несколькими типами?Вы могли бы сделать это EventPublisher<T> и использовать IHandle<T> везде, делая Publish неуниверсальным?Это может или не может быть осуществимо в зависимости от того, как вы хотите использовать это - оба варианта имеют смысл.

2 голосов
/ 17 августа 2011

Нет такой вещи.Открытый шаблон не может быть статически скомпилирован / связан.Он имеет реальный тип (typeof(IHandle<>)), но вы можете работать с ним только с помощью отражения.

Лучшее, что вы можете сделать, это сохранить его как object (или dynamic в .NET 4)или как общий базовый тип IHandle, как вы уже сделали, но в любом случае у вас не будет доступа во время компиляции к событию Handle.Вам придется использовать Reflection или приведение, чтобы вызвать его во время выполнения.

Чтобы связать события в первую очередь, вы можете воспользоваться преимуществами вывода типа в методе ( не конструктор), то есть

public void RegisterHandler(IHandle<TMessage> handler) { ... }

Вызовы метода могут быть написаны без универсального аргумента, если вы передаете действительный универсальный IHandle<TMessage> в качестве параметра.Вы можете использовать это, чтобы ограничить то, что входит в вашу коллекцию неуниверсальных объектов IHandle, а затем использовать методы Reflection (в частности, методы GetInterfaces, GetMethod и MakeGenericMethod) для фактического вызова обработчика.Или, как в вашем примере, если вы уже знаете тип события, просто попробуйте выполнить приведение.

...