Short answer
Despite the name "implicit", implicit conversions don't apply unless the rules explicitly say they do, and the rules don't allow boxing conversions when going from IEnumerable<int>
to IEnumerable<IComparable<int>>
. As a simpler case, you can't go from IEnumerable<int>
to IEnumerable<object>
for the same reason, and that case is well documented.
Long answer
OK, first of all, why would IEnumerable<T>
convert to IEnumerable<IComparable<T>>
at all? This is covered in §6.1.6 (C# Language Specification 5.0):
The implicit reference conversions are:
[...]
- From any reference-type to an interface or delegate type
T
if it has an implicit identity or reference conversion to an interface or
delegate type T0
and
T0
is variance-convertible (§13.1.3.2) to T
.
And §13.1.3.2 says:
A type T<A1, …, An>
is
variance-convertible to a type T<B1, …, Bn>
if T
is either an interface or a delegate type
declared with the variant type parameters T<X1, …, Xn>
,
and for each variant type parameter
Xi
one of the following holds:
Xi
is covariant and an implicit reference or identity conversion exists from Ai
to
Bi
Since IEnumerable<T>
is covariant in T
, this means that if there is an implicit reference or identity conversion from T
to IComparable<T>
, then there is an implicit reference conversion from IEnumerable<T>
to IEnumerable<IComparable<T>>
, by virtue of these being variance-convertible.
I emphasized "reference" for a reason, of course. Since Version
implements IComparable<Version>
, there is an implicit reference conversion:
- From any class-type
S
to any interface-type T
, provided S
implements T
.
Right, so now, why doesn't IEnumerable<int>
implicitly convert to IEnumerable<IComparable<int>>
? After all, int
implicitly converts to IComparable<int>
:
IComparable<int> x = 0; // sure
But it does so not through a reference conversion or an identity conversion, but through a boxing conversion (§6.1.7):
A boxing conversion exists from any non-nullable-value-type [...] to
any interface-type implemented by the non-nullable-value-type.
The rules of §13.1.3.2 do not allow boxing conversions in considering whether a variance conversion is possible, and there is no other rule that would enable an implicit conversion from IEnumerable<int>
to IEnumerable<IComparable<int>>
. Despite the name, implicit conversions are covered by explicit rules.
There is actually a much simpler illustration of this problem:
object x = 0; // sure, an int is an object
IEnumerable<object> x = new int[] { 0 }; // except when it's not
This isn't allowed for the same reason: there is no reference conversion from int
to object
, only a boxing conversion, and those are not considered. And in this form, there are several questions on Stack Overflow that explain why this is not allowed (like this one). To summarize: it's not that this is impossible, but to support it, the compiler would have to generate supporting code to stick the code for the boxing conversion somewhere. The C# team valued transparency in this case over ease of use and decided to allow identity-preserving conversions only.
Finally, as a matter of practical consideration, suppose you had an IEnumerable<int>
and you needed an IEnumerable<IComparable<int>>
, how would you get it? Well, by doing the boxing yourself:
Func<int, IComparable<int>> asComparable = i => i; // compiles to ldarg ; box ; ret
IEnumerable<IComparable<int>> x = Enumerable.Empty<int>().Select(asComparable);
Of course using Enumerable.Cast
would be more practical here; I wrote it this way to highlight that an implicit conversion is involved. There is a cost to this operation, and that's just the point; the C# designers wanted this cost to be explicit.