Reputation: 195
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.
Upvotes: 1
Views: 107
Reputation: 81493
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.
Upvotes: 1