开发者

Cannot add object to list

I'm trying out an example of using Domain Events to notify of when something has happened in a system (borrowed from here and here).

I'm really close to getting the code working how I want, however, I've hit a bit of a brick wall. Here is my DomainEvents class:

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

The above won't compile because Actions.Add cannot accept callback since it's a IEventHandler<T> type rather then a IEventHandler<IDomainEvent> type. Here's some more code to clarify.

This is called from my console application:

DomainEvents.Register(new CustomerHasUnpaidDuesEventHandler());

CustomerHasUnpaidDuesEventHandler implements IEventHandler<CustomerHasUnpaidDuesEvent>, where CustomerHasUnpaidDuesEvent implements 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; }
    }

This is what I don't get - if CustomerHasUnpaidDuesEvent implements IDomainEvent, then why is the call to Actions.Add failing? How can I resolve this?

EDIT:

To make things clearer, here is entire code for my test app:

    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. Jas.


There is no need for your Register method to be generic:

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


Edit:
The problem is that in order to have IEventHandler<CustomerHasUnpaidDuesEvent> to be in the list of IEventHandler<IDomainEvent>s, we need T to be a covariant template parameter in IEventHandler<T> (which is declared as IEventHandler<out T>). However in order to allow the function Handle(T arg), we need T to be contravariant. So strictly this way won't work. Imagine: if we really could insert an IEventHandler<CustomerHasUnpaidDuesEvent> into a list of IEventHandler<IDomainEvent>s, than someone might try to call Handle with the argument of some type which derives from IDomainEvent but is not a CustomerHasUnpaidDuesEvent! This should be impossible to do.

The solution is that we don't need the exact type at Register, so we can keep a reference to a generic base interface. The implementation is here: http://ideone.com/9glmQ

Old answer is not valid, kept below for consistency.


Maybe you need to declare IEventHandler to accept T as a covariant type?

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

Edit: surely CustomerHasUnpaidDuesEvent is an IDomainEvent, but you need IEventHandler<CustomerHasUnpaidDuesEvent> to be a IEventHandler<IDomainEvent>. This is exactly what covariance does. In order to allow that, your template parameter in IEventhandler must be declared covariant (<in T> instead of just <T>).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜