Невозможно добавить объект в список - PullRequest
0 голосов
/ 03 ноября 2010

Я пробую пример использования Domain Events для уведомления, когда что-то произошло в системе (заимствовано из здесь и здесь ).

Я действительно близок к тому, чтобы заставить код работать так, как я хочу, однако я наткнулся на кирпичную стену.Вот мой DomainEvents класс:

 public static class DomainEvents
    {
        [ThreadStatic]
        private static IList<IEventHandler<IDomainEvent>> Actions;

        public static void Register<T>(IEventHandler<T> callback) where T : IDomainEvent
        {
            if (Actions == null)
            {
                Actions = new List<IEventHandler<IDomainEvent>>();
            }

            Actions.Add(callback); // <---- Problem here, since I can't add callback to the collection.
        }

        public static void ClearCallbacks()
        {
            Actions = null;
        }

        public static void Raise<T>(T args) where T : IDomainEvent
        {
            if (Actions == null)
            {
                return;
            }

            foreach (var action in Actions)
            {
                if (action is IEventHandler<T>)
                {
                    ((IEventHandler<T>)action).Handle(args);
                }
            }
        }

Выше не скомпилируется, потому что Actions.Add не может принять callback, так как это тип IEventHandler<T>, а не тип IEventHandler<IDomainEvent>.Вот еще немного кода для пояснения.

Это вызывается из моего консольного приложения:

DomainEvents.Register(new CustomerHasUnpaidDuesEventHandler());

CustomerHasUnpaidDuesEventHandler реализует IEventHandler<CustomerHasUnpaidDuesEvent>, где CustomerHasUnpaidDuesEvent реализует IDomainEvent.

public class CustomerHasUnpaidDuesEventHandler : IEventHandler<CustomerHasUnpaidDuesEvent>
    {
        public IEmailSender EmailSender { get; set; }

        public void Handle(CustomerHasUnpaidDuesEvent @event)
        {
            this.EmailSender.SendEmail(@event.Customer.EmailAddress);
        }
    }

public class CustomerHasUnpaidDuesEvent : IDomainEvent
    {
        public CustomerHasUnpaidDuesEvent(Customer customer)
        {
            this.Customer = customer;
        }

        public Customer Customer { get; set; }
    }

Это то, чего я не получаю - если CustomerHasUnpaidDuesEvent реализует IDomainEvent, то почему происходит сбой вызова на Actions.Add?Как я могу решить эту проблему?

РЕДАКТИРОВАТЬ:

Чтобы прояснить ситуацию, вот весь код для моего тестового приложения:

    class Program
    {
        static void Main()
        {
            DomainEvents.Register(new CustomerHasUnpaidDuesEventHandler());

            var c = new Customer();

            c.EmailAddress = "test@dfsdf.com";

            c.CheckUnpaidDues();
        }
    }


 public interface IEventHandler<in T> where T : IDomainEvent
    {
        void Handle(T args);
    }

 public interface IEmailSender
    {
        void SendEmail(string emailAddress);
    }


 public interface IDomainEvent
    {
    }

public static class DomainEvents
    {
        [ThreadStatic]
        private static IList<IEventHandler<IDomainEvent>> Actions;

        public static void Register<T>(IEventHandler<T> callback) where T: IDomainEvent
        {
            if (Actions == null)
            {
                Actions = new List<IEventHandler<IDomainEvent>>();
            }

            Actions.Add(callback);
        }

        public static void ClearCallbacks()
        {
            Actions = null;
        }

        public static void Raise<T>(T args) where T : IDomainEvent
        {
            if (Actions == null)
            {
                return;
            }

            foreach (IEventHandler<T> action in Actions)
            {
                (action).Handle(args);
            }
        }
    }


public class CustomerHasUnpaidDuesEventHandler : IEventHandler<CustomerHasUnpaidDuesEvent>
    {
        public IEmailSender EmailSender { get; set; }

        public void Handle(CustomerHasUnpaidDuesEvent @event)
        {
            this.EmailSender.SendEmail(@event.Customer.EmailAddress);
        }
    }

public class CustomerHasUnpaidDuesEvent : IDomainEvent
    {
        public CustomerHasUnpaidDuesEvent(Customer customer)
        {
            this.Customer = customer;
        }

        public Customer Customer { get; set; }
    }

public class Customer
    {
        public string Name { get; set; }

        public string EmailAddress { get; set; }

        public bool HasUnpaidDues { get; set; }

        public void CheckUnpaidDues()
        {
            HasUnpaidDues = true;
            DomainEvents.Raise(new CustomerHasUnpaidDuesEvent(this));
        }
    }

Cheers.Иак.

Ответы [ 2 ]

1 голос
/ 03 ноября 2010

Ваш метод Register не должен быть универсальным:

    public static void Register(IEventHandler<IDomainEvent> callback)
    {
        if (Actions == null)
        {
            Actions = new List<IEventHandler<IDomainEvent>>();
        }
        Actions.Add(callback);
    }
1 голос
/ 03 ноября 2010

Edit:
Проблема в том, что для того, чтобы IEventHandler<CustomerHasUnpaidDuesEvent> находился в списке IEventHandler<IDomainEvent> с, нам нужно, чтобы T был ковариантным параметром шаблона в IEventHandler<T> (который объявлен как IEventHandler<out T>). Однако, чтобы разрешить функцию Handle(T arg), нам нужно, чтобы T был контравариантным . Так что строго этот путь не сработает. Представьте себе: если бы мы действительно могли вставить IEventHandler<CustomerHasUnpaidDuesEvent> в список IEventHandler<IDomainEvent> с, то кто-то мог бы попытаться вызвать Handle с аргументом некоторого типа, который происходит от IDomainEvent, но не является CustomerHasUnpaidDuesEvent! Это должно быть невозможно сделать.

Решение состоит в том, что нам не нужен точный тип в Register, поэтому мы можем сохранить ссылку на общий базовый интерфейс. Реализация здесь: http://ideone.com/9glmQ

Старый ответ недействителен, приведен ниже для согласованности.


Может быть, вам нужно объявить IEventHandler для принятия T как ковариантного типа?

interface IEventHandler<in T> where T: IDomainEvent
{
    void Handle();
    // ...
}

Редактировать: конечно, CustomerHasUnpaidDuesEvent - это IDomainEvent, но вам нужно IEventHandler<CustomerHasUnpaidDuesEvent>, чтобы быть IEventHandler<IDomainEvent>. Это именно то, что делает ковариация. Для этого ваш шаблонный параметр в IEventhandler должен быть объявлен ковариантным (<in T> вместо просто <T>).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...