2010-11-02 3 views
0

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

Я действительно близок к тому, что код работает так, как я хочу, однако, я ударил немного кирпичной стены. Вот мой 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 неудачу? Как я могу это решить?

EDIT:

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

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

      var c = new Customer(); 

      c.EmailAddress = "[email protected]"; 

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

Приветствия. Jas.

+0

[Это] (http://stackoverflow.com/questions/1259104/ilist-using-covariance-and-contravariance-in- c-is-this-possible) должна быть связанной проблемой. – Vlad

+0

@ Vlad - Извините, что вам больно, но можете ли вы помочь мне с примером кода? Это «ковариация» и «контравариантность» для меня очень сбивает с толку. Как я могу изменить свой код, чтобы использовать эти методы для решения моей проблемы? –

+0

Как объявляется 'IEventHandler'? Это системный интерфейс или ваш локальный? – Vlad

ответ

1

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

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

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


Возможно, вам нужно объявить IEventHandler, чтобы принять T как ковариантный тип?

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

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

+0

Просто попробовал это, но у меня такая же проблема компиляции с 'Actions.Add()'. Хм, это сложно для меня. –

+0

Хм, что сообщение об ошибке? Не могли бы вы разместить свой код IEventHandler? – Vlad

+0

Если это ковариант, тогда это должно быть 'out T'. ('in' является контравариантным) –

1

Там нет необходимости в ваш метод Зарегистрируйся, чтобы быть универсальным:

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

Если я это сделаю, тогда я получаю ошибку компиляции в этой строке в моем консольном приложении - «DomainEvents.Register» (новый CustomerHasUnpaidDuesEventHandler()); ' –

+0

, что произойдет, если вы измените« класс CustomerHasUnpaidDuesEventHandler » : IEventHandler 'to'class CustomerHasUnpaidDuesEventHandler: IEventHandler '? –

Смежные вопросы