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

Time:01-10

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?

CodePudding user response:

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.

  •  Tags:  
  • Related