Search code examples
c#genericstype-conversioncovariancevalue-type

Covariance error on Valued Type interface


I have a generic interface holding a covariant TValue parameter and an abstract class that does some repetitive stuff to liberate the child classes from that burden. Then I have 2 subclasses that extend from that abstract one, the first setting the generic parameter as a string and the second as an int.

This is a subpiece of code taken from the project, overly simplified just to focus on the matter.

public interface IElement<out TValue>
{
    string Name { get; }
    TValue Value { get; }
}

public abstract class Element<TValue> : IElement<TValue>
{
    public string Name { get; }
    public TValue Value { get; set; }

    public Element(string name)
    {
        Name = name;
    }
}

public class Name : Element<string>
{
    public Name() : base("Name") { }
}

public class Height : Element<int>
{
    public Height() : base("Height") { }
}

Basically - and this is not what I'm doing in my code, but illustrates fairly simply the problem I'm having - if I try to assign Name to an IElement holding object like this:

IElement<object> element = new Name();

It succeeds as I had expected since the TValue parameter in IElement is covariant. However if I set it to Height:

IElement<object> element = new Height();

I get an Cannot implicitly convert type 'Height' to 'IElement<object>'. An explicit conversion exists (are you missing a cast?) error.

Now, I don't know why this works with a class that sets the generic parameter as a string, but not with a an int (or enum as I have also in the project some enums). Is it because string is a class and int is a struct?

Any help is greatly appreciated.


Solution

  • Simply, because one is a value type. The CLR disallows it as it will need to preserve its identity, whereas boxing does not.

    Eric Lippert has a great blog about this at Representation and identity

    covariant and contravariant conversions of interface and delegate types require that all varying type arguments be of reference types. To ensure that a variant reference conversion is always identity-preserving, all of the conversions involving type arguments must also be identity-preserving. The easiest way to ensure that all the non-trivial conversions on type arguments are identity-preserving is to restrict them to be reference conversions.

    Additionally, you can read a lot more about identity, conversion, generics and variance in the specs at various places

    11.2.11 Implicit conversions involving type parameters

    For a type-parameter T that is not known to be a reference type (§15.2.5), the following conversions involving T are considered to be boxing conversions (11.2.8) at compile-time. At run-time, if T is a value type, the conversion is executed as a boxing conversion. At run-time, if T is a reference type, the conversion is executed as an implicit reference conversion or identity conversion.