Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

collection of different generic types

Tags:

c#

generics

Given the following interface:

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

What IEnumerable type can I use to store a collection of IEventHandler<TEvent> implementations where TEvent is different?

i.e. Given the following 3 implementations:

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

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

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

Can I do any better than a collection of objects?

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

BTW, have seen some other answers advocating the use of a base type or inherited non-generic interface but cannot see how that would add a huge amount of value in this case unless I am missing something. Yes, it would let me add them all to the collection in a slightly more type safe way that using object, but would not let me iterate over them and call the strongly typed Process method without casting just as I need to do with object.

public interface IEventHandler
{        
}

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

I still need to cast if I have IEnumerable<IEventHandler> or IEnumerable<obect>

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

Any thoughts on how to improve this?

like image 939
Paul Hiles Avatar asked Nov 19 '12 15:11

Paul Hiles


2 Answers

I think the best approach here is to use the OfType extension method and keep your List, assuming the type of the event is known at compile time; there will still be a cast, but you will not be doing it and you will only get the entries that can actually handle that event.

like image 117
fsimonazzi Avatar answered Oct 19 '22 23:10

fsimonazzi


Probably your design is not optimal. Try to move all code requiring an event type specific behavior to the events, instead of implementing it in the event handler. I.e. let polymorphism work for you.

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

As an example let's assume that you need to create a message depending on event specific properties. Instead of constructing the message inside of the event handler, construct it in the event

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

A specific event

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

Let's assume that there was a way of defining a list the way you intend

var handlers = new List<?>();

How would you cast?

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

Maybe a better way would be to have one list per event type

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

    ...
}

Then you can access an event handler like this

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

UPDATE #2

A completely different solution creates a new collection class that encapsulates the weakly typed list and provides a strongly typed interface by using generic methods

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>();
like image 35
Olivier Jacot-Descombes Avatar answered Oct 19 '22 22:10

Olivier Jacot-Descombes