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?
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.
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>();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With