Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why does the C# 11 compiler reject a generic parameter that it accepts as the source of an assignment to the same type?

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.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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?

>Solution :

In short – add class constraint:

public class AnimalHandlerWithDefaultSuccessfulResult<TAnimal> : AnimalHandler<TAnimal, DefaultSuccessfulResult<TAnimal>>
    where TAnimal : class, IAnimal { }

The reason being that variance in C# is supported only for reference types. From the docs on generics variance:

Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type.

You will be able to "confirm" the case by changing Dog record to be a value type (using C# 10’s record structs):

public record struct Dog() : IAnimal; 

DefaultSuccessfulResult<Dog> source = new();
// Following produces compiler error:
// Cannot implicitly convert type 'DefaultSuccessfulResult<Dog>' to 'IAnimalOperationResult<IAnimal>'. 
// An explicit conversion exists (are you missing a cast?)
IAnimalOperationResult<IAnimal> target = source;
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading