2012-11-19 22 views
5

otrzymuje następujące interfejs:zbiór różnych typów generycznych

public interface IEventHandler<in TEvent> where TEvent : IEvent 
{ 
    void Process(TEvent @event); 
} 

Jakiego typu IEnumerable można używać do przechowywania kolekcji IEventHandler<TEvent> wdrożeń gdzie TEvent jest inaczej?

tj Biorąc pod uwagę następujące 3 implementacje:

public class BlahEvent1EventHandler : IEventHandler<Event1> 
{ 
    ... 
} 

public class WhateverEvent1EventHandler : IEventHandler<Event1> 
{ 
    ... 
} 

public class BlahEvent2EventHandler : IEventHandler<Event2> 
{ 
    ... 
} 

mogę zrobić nic lepszego niż zbiór obiektów?

 var handlers = new List<object> 
          { 
           new BlahEvent1EventHandler(), 
           new WhateverEvent1EventHandler(), 
           new BlahEvent2EventHandler(), 
          }; 

BTW, widziałem kilka innych odpowiedzi propagującej korzystanie z typu podstawowego lub odziedziczonego interfejsu non-rodzajowego, ale nie może zobaczyć, jak to będzie dodać ogromną ilość wartości w tym przypadku chyba że ja czegoś brakuje. Tak, pozwoliłoby mi to dodać je wszystkie do kolekcji w nieco bezpieczniejszy sposób niż przy użyciu obiektu, ale nie pozwoliłoby mi na ich powtórzenie i wywołanie mocno napisanej metody procesu bez rzutowania, tak jak potrzebuję zrobić z obiektem.

public interface IEventHandler 
{   
} 

public interface IEventHandler<in TEvent> : IEventHandler where TEvent : IEvent 
{ 
    void Process(TEvent @event); 
} 

nadal muszę rzucać jeśli mam IEnumerable<IEventHandler> lub IEnumerable<obect>

foreach (var handler in _handlers.Cast<IEventHandler<TEvent>>()) 
{ 
    handler.Process(@event); 
} 

wszelkie myśli o tym, jak poprawić?

+0

Nie sądzę, nie ma na odwrót. Chętnie posłuchałbym interesujących informacji na ten temat, ponieważ prawie codziennie gram w tym samym numerze. –

+0

Protip: nie nazwij zmiennych tak jak słowa kluczowe w języku. – tomfanning

+1

Ponieważ elementy różnych typów na liście wymagałyby wywoływania różnych metod "Procesowych" z różnymi typami argumentów, jak byś je nazwał? Zauważ, że w tym przypadku nie wystąpiłaby żadna metoda zabezpieczenia przed przeciążeniem, ponieważ typ elementu listy jest znany tylko w środowisku wykonawczym. Celem generycznych jest dostarczenie statycznego typu znanego w czasie kompilacji. –

Odpowiedz

0

Jeśli trzeba wyliczyć poprzez swoje wozy i wywołać jej członek interfejsu Process, można realizować swoje zajęcia tak:

public class BlahEvent1EventHandler : IEventHandler<IEvent> 
{ 
    ... 
} 

public class WhateverEvent1EventHandler : IEventHandler<IEvent> 
{ 
    ... 
} 

public class BlahEvent2EventHandler : IEventHandler<IEvent> 
{ 
    ... 
} 

public class Event1 : IEvent 
{} 

public class Event2 : IEvent 
{} 

następnie wykorzystać je:

List<IEventHandler<IEvent>> list = new List<IEventHandler<IEvent>>(); 

list.Add(new BlahEvent1EventHandler()); 

foreach (IEventHandler<IEvent> eventHandler in list) 
{ 
eventHandler.Process(new Event1()); 
} 
+0

Procedury obsługi zdarzeń wymagają silnie wpisanego zdarzenia, więc nie mogę dziedziczyć po IEventHandler . Wymagałoby to ode mnie w każdym programie obsługi. –

3

Myślę, że najlepszym rozwiązaniem tutaj należy użyć metody rozszerzenia OfType i zachować swoją Listę, zakładając, że typ zdarzenia jest znany podczas kompilacji; nadal będzie rzutowanie, ale nie będziesz tego robił, a otrzymasz tylko te wpisy, które mogą obsłużyć to wydarzenie.

1

Prawdopodobnie Twój projekt nie jest optymalny. Spróbuj przenieść cały kod wymagający zachowania określonego typu zdarzenia do zdarzeń, zamiast implementować go w procedurze obsługi zdarzeń. To znaczy. pozwól polimorfizmowi pracować dla ciebie.

public interface IEventHandler 
{ 
    void Process(IEvent evt); 
} 

Jako przykład załóżmy, że musisz utworzyć wiadomość w zależności od właściwości specyficznych dla zdarzenia. Zamiast budowy wiadomość wewnątrz obsługi zdarzeń, zbudować go w przypadku

public interface IEvent 
{ 
    string Message { get; } 
    ... 
} 

określonego zdarzenia

public class TestEvent : IEvent 
{ 
    public string A { get; set; } // Event specific property 
    public string B { get; set; } // Event specific property 

    public string Message { get { return String.Format("{0} {1}", A, B); } } 
    // The event handler does not need to access A or B. 
} 

UPDATE

Załóżmy, że istnieje sposób, definiowania listy zgodnie ze sposobem, w jaki zamierzasz uzyskać

var handlers = new List<?>(); 

Jak byś rzucił?

var handler = handlers[i]; 
// How to cast? 
((?)handler).Process((?)evt); 

Być może lepszym sposobem byłoby mieć jedną listę za typ zdarzenia

public static class EventHandler<in TEvent> : IEventHandler<TEvent> 
    where TEvent : IEvent 
{ 
    public static readonly List<IEventHandler<TEvent>> Handlers = 
     new List<IEventHandler<TEvent>>(); 

    ... 
} 

Następnie można uzyskać dostęp do programu obsługi zdarzeń jak tego

SpecificEventType specEvent = ...; 
EventHandler<SpecificEventType>.Handlers[0].Process(specEvent); 

AKTUALIZACJA # 2

Zupełnie inne rozwiązanie tworzy nową klasę kolekcji, która zamyka słabo wpisane listę i zapewnia silnie typami interfejsu za pomocą metody rodzajowe

public class HandlerCollection : IEnumerable 
{ 
    private readonly List<object> _handlers = new List<object>(); 

    public void Add<TEvent>(IEventHandler<TEvent> handler) 
     where TEvent : IEvent 
    { 
     _handlers.Add(handler); 
    } 

    public IEventHandler<TEvent> Find<TEvent>() 
     where TEvent : IEvent 
    { 
     return _handlers 
      .OfType<IEventHandler<TEvent>>() 
      .FirstOrDefault(); 
    } 

    public IEventHandler<TEvent> Find<TEvent>(Func<IEventHandler<TEvent>, bool> predicate) 
     where TEvent : IEvent 
    { 
     return _handlers 
      .OfType<IEventHandler<TEvent>>() 
      .Where(predicate) 
      .FirstOrDefault(); 
    } 

    // Collection initializers can only be applied to types implementing IEnumerable 
    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return _handlers.GetEnumerator(); 
    } 
} 

test

var handlers = new HandlerCollection { 
        new BlahEvent1EventHandler(), 
        new WhateverEvent1EventHandler(), 
        new BlahEvent2EventHandler() 
       }; 
IEventHandler<WhateverEvent1> eh = handlers.Find<WhateverEvent1>(); 
+0

Wydaje się to negować aspekt oddzielania przy użyciu zdarzeń w pierwszej kolejności. Może istnieć wiele programów obsługi zdarzeń przetwarzających pojedyncze zdarzenie i dodających nową akcję. Wymagana jest tylko nowa klasa zdarzeń, która zostanie automatycznie pobrana przez kontener IoC. Ładne i czyste i SOLIDNE. Dzięki temu podejściu klasa zdarzeń musi wszystko wiedzieć, a obsługa zdarzenia w dodatkowy sposób wiąże się z modyfikacją klasy zdarzenia. Nie jestem pewien, czy to lubię. –

+0

@ Flower Guy: Ok, ale jak zamierzasz nazwać właściwą obsługę zdarzeń, jeśli zależy to od rodzaju zdarzenia? Będziesz potrzebował instrukcji switch, która obejmie wszystkie typy zdarzeń. Nie bardzo zorientowany na obiekt. Różne programy obsługi zdarzeń mogą nadal decydować się na użycie "Message", ale nie powinny zależeć od "A" lub "B". –

+0

Do tego służy funkcja FunC > eventHandlerFactory. Przekaż to wydarzenie, a otrzymasz kolekcję zgodnych procedur obsługi zdarzeń, dzięki czemu możesz po prostu przejrzeć je. Problem polega na tym, że fabryka jest wstrzykiwana do klasy, aby można ją było wykorzystać później, gdy wydarzenie jest faktycznie wywoływane i obsługiwane. Muszę wstrzyknąć Func przy starcie. Func jest po prostu sposobem na uniknięcie wstrzyknięcia rzeczywistego kontenera IoC, który wydaje się wykonywać wiele implementacji zdarzeń domeny. –

Powiązane problemy