Search code examples
c#.netgenericsinterfacecovariance

Contra/Co Variance for a list of generic types assuming base interface inheritance


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!


Solution

  • 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.