Home > Software engineering >  Type parameter must be contravariantly valid
Type parameter must be contravariantly valid

Time:07-31

Consider a bird:

public interface IBird
{

}

public class Duck : IBird
{
    public string DuckyProperty { get; set; } //this property is Duck specific!
}

And a bird processor:

public interface IBirdProcessor<out T> where T : IBird
{
    double GetBirdWeight(T obj); //this does not compile
}

public class DuckProcessor : IBirdProcessor<Duck>
{
    public double GetBirdWeight(Duck duck)
    {
        return double.Parse(duck.DuckyProperty);
    }
}

And a factory to get a bird processor:

public class BirdProcessorFactory
{
    public IBirdProcessor<T> GetBirdProcessor<T>(T obj) where T : IBird
    {
        return (IBirdProcessor<T>)new DuckProcessor();
    }
}

Startup:

static void Main()
{
    var bird = new Duck() { DuckyProperty = "23" } as IBird; //the cast is necessary to simulate a real world scenario
    var factory = new BirdProcessorFactory();
    var provider = factory.GetBirdProcessor(bird);

    var weight = provider.GetBirdWeight(bird);
}

I want to have an abstract generic bird factory but I get the following error instead:

The type parameter 'T' must be contravariantly valid on 'Program.IBirdProcessor<T>.GetBirdWeight(T)'. 'T' is covariant

If I remove 'out' keyword then I get:

System.InvalidCastException: 'Unable to cast object of type 'DuckProcessor' to type 'IBirdProcessor`1[NotVegetables.Program IBird]'.'

The cast won't work!

As a workaround I can do this:

public class DuckProcessor : IBirdProcessor<IBird>
{
    public double GetBirdWeight(IBird bird)
    {
        var duck = bird as Duck;
        return double.Parse(duck.DuckyProperty);
    }
}

But this defies usage of generics at all, I feel like DuckProcessor should work with ducks, not with abstract birds.

Am I doing something completely wrong?

CodePudding user response:

All the types in C# generics have to be known at compile time. Only IBird is known at compile time, and the factory has to return an IBirdProcessor<IBird>. Since covariancy isn't an option (because DuckProcessor.GetBirdWeight only accepts Ducks), a DuckProcessor cannot be returned.

You can kind of get the behavior you are wanting with:

public interface IBirdProcessor
{
  double GetBirdWeight(IBird obj);
}

public abstract class BirdProcessorBase<T> : IBirdProcessor where T : IBird
{
  public double GetBirdWeight(IBird bird) => GetBirdWeightInternal((T)bird);

  protected abstract double GetBirdWeightInternal(T bird);
}

public class DuckProcessor : BirdProcessorBase<Duck>
{
  protected override double GetBirdWeightInternal(Duck duck)
  {
    return double.Parse(duck.DuckyProperty);
  }
}

public class BirdProcessorFactory
{
  public IBirdProcessor GetBirdProcessor(IBird bird)
  {
    if (bird.GetType().IsAssignableTo(typeof(Duck)))
      return new DuckProcessor();

    throw new Exception($"No processor for {bird.GetType().Name}");
  }
}

Though if you ever call the processor returned from the factory with a type that doesn't match, it will throw exceptions at you.

  • Related