Search code examples
c#contravariance

Why can't I take a contravariant interface as a parameter to a method on the interface?


I'm trying to setup a CoR with interfaces where a handler in the chain can be a for a less derived event type using contravariance. I create this interface to do it.

public interface IHandler<in TEvent>
{
    void SetNext(IHandler<TEvent> next);
    void Handle(TEvent event);
}

It fails with the compiler error

CS1961 Invalid variance: The type parameter 'TEvent' must be covariantly valid on 'IHandler.SetNext(IHandler)'. 'TEvent' is contravariant.

I thought this should be valid since IHandler is covariant in TEvent. What am I missing?


Solution

  • It makes a lot of sense if you think in terms of Animals and Dogs.

    Let's make up some concrete implementations first:

    class AnimalHandler: IHandler<Animal> { 
        public void SetNext(IHandler<Animal> next) {
            // animal handlers can handle cats :)
            next.Handle(new Cat());
        }
    
        // ...
    }
    
    class DogHandler: IHandler<Dog> { ... }
    

    Since the type parameter of IHandler is contravariant, a IHandler<Animal> is a kind of IHandler<Dog>:

    IHandler<Animal> someAnimalHandler = new AnimalHandler();
    IHandler<Dog> dogHandler = someAnimalHandler;
    

    Suppose that your SetNext method is allowed to be declared in IHandler, then I can call SetNext with another IHandler<Dog> on this dogHandler, since that is exactly the type that dogHandler.SetNext accepts.

    IHandler<Dog> anotherDogHandler = new DogHandler();
    dogHandler.SetNext(anotherDogHandler);
    

    But that shouldn't be possible! After all, dogHandler actually stores an instance of AnimalHandler, whose SetNext method accepts IHandler<Animal>, and a IHandler<Dog> is not necessarily a IHandler<Animal>, is it? Imagine what would happen if the implementation of AnimalHandler.SetNext actually runs - keep in mind that the next parameter is a DogHandler - we would be asking a dog handler to handle a cat!

    Therefore, it is invalid to declare SetNext with a contravariant TEvent.

    This is specified more precisely here. It is said that the type IHandler<TEvent> is input-unsafe, and so is prohibited as a parameter.

    TEvent would have to be invariant for both of your interface methods to compile.