Whilst trying to get the C# compiler to do as much work as possible, I usually end up using (some might say abusing) genericity.
There is one particular situation I find very often and I'm not able to explain why. It would be great to have an explanation similar to Eric Lippert's brilliant answer to this similar - but not the same, as far as I can see - question: https://stackoverflow.com/a/17440148/257372
I have adapted the names of the real classes to use Animal so that it matches the answer above. I have also removed all methods and any other unnecessary details in order to keep things as simple as possible.
public interface IAnimal { }
public interface IAnimalOperationResult<out TAnimal> where TAnimal : IAnimal { }
public record DefaultSuccessfulResult<TAnimal>() : IAnimalOperationResult<TAnimal> where TAnimal : IAnimal;
public abstract class AnimalHandler<TAnimal, TSuccessfulAnimalOperationResult> where TAnimal : IAnimal
where TSuccessfulAnimalOperationResult : IAnimalOperationResult<IAnimal> { }
// The compiler complains here with the following message:
// Error CS0311: The type 'DefaultSuccessfulResult<TAnimal>' cannot be used as type parameter 'TSuccessfulAnimalOperationResult' in the generic type or method 'AnimalHandler<TAnimal, TSuccessfulAnimalOperationResult>'.
// There is no implicit reference conversion from 'DefaultSuccessfulResult<TAnimal>' to 'IAnimalOperationResult<IAnimal>'
public class AnimalHandlerWithDefaultSuccessfulResult<TAnimal> : AnimalHandler<TAnimal, DefaultSuccessfulResult<TAnimal>>
where TAnimal : IAnimal { }
The error message says There is no implicit reference conversion from 'DefaultSuccessfulResult<TAnimal>' to 'IAnimalOperationResult<IAnimal>'
Which, according to the compiler, is not true, since it accepts the following code:
public record Dog() : IAnimal;
[Fact]
public void CanAssignValues()
{
DefaultSuccessfulResult<Dog> source = new();
// This assignment requires the same implicit reference conversion the compiler claims doesn't exist.
// However, in this instance, the compiler accepts it.
IAnimalOperationResult<IAnimal> target = source;
}
I'm obviously missing something, but what?