2012-11-19 20 views
5

Angesichts der folgende Schnittstelle:Sammlung von verschiedenen generischen Typen

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

Welche IEnumerable-Typ kann ich eine Sammlung von IEventHandler<TEvent> Implementierungen speichern verwenden, wo TEvent anders ist?

das heißt die folgenden 3-Implementierungen Gegeben:

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

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

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

Kann ich besser als eine Sammlung von Objekten?

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

BTW, haben einige andere Antworten befürworten die Verwendung eines Basistyp oder geerbt nicht-generische Schnittstelle zu sehen, kann aber nicht sehen, wie das eine riesige Menge an Wert in diesem Fall hinzufügen würde, es sei denn ich bin etwas fehlt. Ja, es würde mir erlauben, sie alle in der Sammlung in einer etwas typsichereren Art und Weise als Objekt hinzuzufügen, aber ich würde nicht über sie iterieren und die stark typisierte Process-Methode ohne Casting aufrufen, so wie ich es mit dem Objekt tun müsste.

public interface IEventHandler 
{   
} 

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

Ich brauche immer noch, wenn ich IEnumerable<IEventHandler> oder IEnumerable<obect> haben zu werfen

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

Irgendwelche Gedanken darüber, wie dies zu verbessern?

+0

Ich glaube nicht, das schwach typisierte Liste und bietet kapselt es ein Weg herum. Ich würde gerne einige interessante Inputs dazu hören, da ich fast täglich in der gleichen Ausgabe laufe. –

+0

Protip: Benennen Sie Ihre Variablen nicht wie bei den Sprachbegriffen. – tomfanning

+1

Da Elemente unterschiedlicher Typen in der Liste den Aufruf einer anderen 'Prozess'-Methode mit unterschiedlichen Argumenttypen erfordern, wie würden Sie sie nennen? Beachten Sie, dass hier keine Methodenüberladung auftreten würde, da der Typ eines Listenelements nur zur Laufzeit bekannt ist. Der Zweck von Generika ist das Bereitstellen eines statischen Typs, der zur Kompilierungszeit bekannt ist. –

Antwort

0

Wenn Sie durch Ihre Handler benötigen aufzuzählen und ihre Prozess-Schnittstelle Mitglied anrufen, dann können Sie Ihre Klassen wie folgt implementieren:

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

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

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

public class Event1 : IEvent 
{} 

public class Event2 : IEvent 
{} 

sie dann verwenden:

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

list.Add(new BlahEvent1EventHandler()); 

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

Die Ereignisbehandlungsroutinen benötigen das stark typisierte Ereignis, sodass ich IEventHandler nicht erben kann. Dies würde erfordern, dass ich jeden Hundeführer einspreche. –

3

Ich denke, der beste Ansatz Hier verwenden Sie die Erweiterungsmethode OfType und behalten Ihre Liste bei, vorausgesetzt, der Typ des Ereignisses ist zur Kompilierzeit bekannt; Es wird immer noch eine Besetzung geben, aber Sie werden es nicht tun und Sie werden nur die Einträge erhalten, die dieses Ereignis tatsächlich behandeln können.

1

Wahrscheinlich ist Ihr Design nicht optimal. Versuchen Sie, den gesamten Code, der ein ereignistypspezifisches Verhalten erfordert, in die Ereignisse zu verschieben, anstatt ihn im Ereignishandler zu implementieren. I.e. Lassen Sie Polymorphismus für Sie arbeiten.

Als Beispiel nehmen wir an, dass Sie abhängig von ereignisspezifischen Eigenschaften eine Nachricht erstellen müssen. Statt die Nachricht innerhalb der Event-Handler für den Bau, bauen sie im Falle

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

Ein bestimmtes Ereignis

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

Lassen Sie uns davon ausgehen, dass es einen Weg Definieren einer Liste, wie Sie beabsichtigen

var handlers = new List<?>(); 

Wie würdest du spielen?

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

Vielleicht eine bessere Möglichkeit, eine Liste pro Ereignistyp

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

    ... 
} 

Dann können Sie einen Event-Handler zugreifen wie diese

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

zu haben wäre UPDATE # 2

Eine ganz andere Lösung schafft eine neue Kollektion Klasse, die eine stark typisierte Schnittstelle unter Verwendung von generischen Methoden

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

Dies scheint den Entkopplungsaspekt der Verwendung von Ereignissen an erster Stelle zu negieren. Es könnte mehrere Ereignisbehandlungsroutinen geben, die ein einzelnes Ereignis verarbeiten, und um eine neue Aktion hinzuzufügen, ist lediglich eine neue Ereignisbehandlungsroutine erforderlich, die automatisch vom IoC-Container abgeholt wird. Schön und sauber und solide. Bei Ihrer Herangehensweise muss die Ereignisklasse alles wissen und die Behandlung eines Ereignisses in einer zusätzlichen Weise beinhaltet das Ändern der Ereignisklasse. Nicht sicher, dass ich das mag. –

+0

@Der Flower Guy: Ok, aber wie willst du den richtigen Event-Handler aufrufen, wenn es vom Ereignistyp abhängt? Sie müssen eine switch-Anweisung haben, die alle Ereignistypen abdeckt. Nicht sehr objektorientiert. Verschiedene Event-Handler können sich immer noch für die Verwendung der "Message" entscheiden oder nicht, sollten aber nicht von "A" oder "B" abhängig sein. –

+0

Das ist, was das Func > eventHandlerFactory für ist. Übergeben Sie das Ereignis und Sie erhalten eine Sammlung von kompatiblen Event-Handlern zurück, so dass Sie sie einfach durchlaufen können. Das Problem besteht darin, dass diese Fabrik in die Klasse eingefügt wird, um sie später zu verwenden, wenn ein Ereignis tatsächlich ausgelöst und behandelt wird. Ich muss den Func beim Start injizieren. Der Func ist im Grunde nur eine Möglichkeit, den tatsächlichen IoC-Container nicht zu injizieren, was viele Domain-Event-Implementierungen zu tun scheinen. –

Verwandte Themen