in .NET I have a three level inheritance using generics with a pattern like:
public interface IManager<TEvent> where TEvent : IEvent
{
void Foo( TEvent mySpecificEvent );
//...
}
class Manager<TEvent> : IManager<TEvent> where TEvent : IEvent
class NumericManager<TEvent> : Manager<TEvent> where TEvent : IEvent
class SpecificNumericManager : NumericManager<ISpecificNumericEvent>
This way the specific managers can handle specific types with the hopes that I can hold several kinds of specific types in a list and tell them all to run Foo(TEvent mySpecificEvent)
operations on their specific events as long as the event inherits from IEvent
Everything builds fine when but I try to treat my SpecificNumericManager
as an IManager<IEvent>
to add it to a List<IManager<IEvent>>
I get a runtime error of:
"Unable to cast object of type SpecificNumericManager
to type IManager<IEvent>
"
Note my code is in VB but I have translated to C# hoping for more support, so if I goofed something small it might be explained by that.
I have experimented and I can successfully cast my SpecificNumericManager<ISpecificNumericEvent>
to IManager<ISpecificNumericEvent>
but just not directly to <Manager<IEvent>>
It seems like contravariance and covariance of generic types was coming up in my first swing at finding out what I can do to fix it, but I haven't found out that the trick is.
Any input is appreciated. Thanks!
What you need is for you IManager
to be covariant.
interface IManager<out TEvent> where TEvent : IEvent
That way this assignment becomes legal:
IManager<IEvent> manager = new Manager<IMoreSpecificEvent>();
Covariance, in the most basic sense, specifies that you don't care if the type parameter is more derived than the constraint for such assignment. That means that, among other things, nowhere in a covariant implementation (so in your Manager You can read more about covariance in C# here.
EDIT:
Right, so this method:
void Foo(TEvent mySpecificEvent);
ruins our little plan here. Right now your Manager
is not covariant, and for a good reason. Let's consider a list:
List<IManager<IEvent>> list;
and say that this assignment is legal (it is not, but let's pretend) :
IManager<ISpecificNumericEvent> manager = new SpecificNumericManager();
list.Add(manager);
What happens if we pass this list around to do some operations on the manager? Well, all we know is that a thing inside is of type IManager<IEvent>
. That means that we should be able to feed it any IEvent
into the Foo
method, right?
class BaseEvent : IEvent {}
foreach(var manager in list)
{
IEvent event = new BaseEvent();
manager.Foo(event);
}
But since we have earlier assigned a SpecificNumericManager
to the list, we're trying to call a method with a signature:
void Foo(TEvent event) where TEvent : ISpecificNumericEvent
with a BaseEvent
, which does not implement ISpecificNumericEvent
. Boom, the type system is destroyed in a fiery explosion, cats and dogs living together, mass hysteria.
This interface cannot be covariant for the reasons given above - C# simply disallows the bypassing of the type system earlier, disallowing a covariant assignment toIManager<IEvent>
. For the same reasons a for-loop feeding events to generic IManager<IEvent>
s would be a bad idea, because there is simply no idea of knowing whether a particular manager will be able to handle that particular event at compile-time. You need to think about what you're really trying achieve with that loop. Usually, when covariance/contravariance rules are broken, it's because the thing you want to do really does not make sense. If, however, you want to do some crazy stuff on types on a generic IManager<IEvent>
list, then you won't have the type system on your side and will have to use reflection to achieve your insidious goals.