Search code examples
.netgenericsinheritancecovariancecontravariance

Why does this .NET class not accept this other parent class as a type parameter via inheritance?


I'm trying to figure out why the compiler is complaining about this (what I thought was a simple) inheritance scenario:

Given this abstract class..

public class AbstractAnimalValidator<TAnimal> : AbstractValidator<TAnimal> 
    where TAnimal: Animal<IFoo, IBar>, new()
{
}

I then try to create this concrete class...

public class CatValidator : AbstractAnimalValidator<Cat>{ }

I get this error...

enter image description here

Ok, so what's a Cat?

public abstract class Animal<TFoo, TBar>
    where TFoo : IFoo
    where TBar : IBar { }

public class Cat : Animal<RedFoo, CleanBar> { }

I just don't get it :/ the Cat is of those two types ......

Now this is the FULL REPO on .NET Fiddle to show a live example of this.

Update 1:

I guess i'm also asking for is 1. why doesn't my code work? Ie. My brain says that should work, but the compiler says: given this scenario XXX .. I wouldn't know what to do ... 2. How to fix this up, so i can learn.

basically, i've read the covariance/contravariance stuff a number of times and my head keeps assploding ... especially with some crap foo/bar examples. Hopefully, with my a-little-bit-more-concrete example, I might just be able to grok this a wee bit more.


Solution

  • This is because Cat is a subclass of Animal<RedFoo, CleanBar>, AbstractAnimalValidator expects something that is a subtype of Animal<IFoo, IBar> but Animal<RedFoo, CleanBar> is not a subtype of Animal<IFoo, IBar> and thus Cat is also not a subtype.

    Please note that in this terminology subtype is a more general term then subclass. B is a subclass of A if B inherits A, and B is a subtype of A if a B type object can be assigned to an A type variable.

    To fix this make the TFoo and TBar type parameters Covariant. This is only allowed for interfaces in C#, so you need to introduce a new interface:

    public interface IAnimal<out TFoo, out TBar>
      where TFoo : IFoo
      where TBar : IBar { }
    

    And use it like this:

    public class AbstractAnimalValidator<TAnimal>
    where TAnimal : IAnimal<IFoo, IBar>, new()
    {
    }
    
    public abstract class Animal<TFoo, TBar> : IAnimal<TFoo, TBar>
      where TFoo : IFoo
      where TBar : IBar { }
    

    This way Cat becomes a subtype of IAnimal<IFoo, IBar>, because RedFoo is a subclass of IFoo and CleanBaris a subclass of IBar.