2

IEnumerable<T> interface is covariant, so IEnumerable<string> is IEnumerable<object>. But why IEnumerable<int> is not IEnumerable<object> while int is object?

Alex Broitman
  • 128
  • 1
  • 10
  • 10
    From https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/variance-in-generic-interfaces: "Variance in generic interfaces is supported for reference types only. Value types do not support variance. For example, `IEnumerable` cannot be implicitly converted to `IEnumerable`, because integers are represented by a value type." – Jon Skeet Nov 28 '22 at 20:28
  • Not exactly a duplicate, https://stackoverflow.com/questions/1793357/do-value-types-integer-decimal-boolean-etc-inherit-from-object, but it's basically int's are structs not objects – gilliduck Nov 28 '22 at 20:29
  • 2
    Or if you want a spec reference: https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/interfaces.md#17233-variance-conversion - the important part being "and an implicit reference or identity conversion exists from Aᵢ to Bᵢ" - there's no *reference or identity* conversion from `int` to `object`. – Jon Skeet Nov 28 '22 at 20:29
  • Does this answer your question? [Do value types (Integer, Decimal, Boolean, etc...) inherit from Object?](https://stackoverflow.com/questions/1793357/do-value-types-integer-decimal-boolean-etc-inherit-from-object) – gilliduck Nov 28 '22 at 20:30
  • 1
    If it helps, [`IEnumerable`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.ienumerable-1) implements `IEnumerable`. So if you're looking for a common interface, both `IEnumerable` and `IEnumerable` are `IEnumerable`. – Gabriel Luci Nov 28 '22 at 20:33
  • 1
    Now with docs linked by @JonSkeet should we convert this question "why C#/.Net decided to limit variance to reference types" or write wiki answer with 100% text from the docs (which is not really suitable for SO)? – Alexei Levenkov Nov 28 '22 at 20:35

1 Answers1

1

Variance conversions like this are only supported if there is an implicit reference or identity conversion between the type parameters. As the spec says:

A type T<Aᵢ, ..., Aᵥ> is variance-convertible to a type T<Bᵢ, ..., Bᵥ> if T is either an interface or a delegate type declared with the variant type parameters T<Xᵢ, ..., Xᵥ>, and for each variant type parameter Xᵢ one of the following holds:

  • Xᵢ is covariant and an implicit reference or identity conversion exists from Aᵢ to Bᵢ
  • Xᵢ is contravariant and an implicit reference or identity conversion exists from Bᵢ to Aᵢ
  • Xᵢ is invariant and an identity conversion exists from Aᵢ to Bᵢ

There is neither a reference not identity conversion between int and object, only a boxing conversion.

You might be wondering what's so special about implicit reference and identity conversions, that they allow variance conversions. Well, these conversions don't require the runtime to do anything at all!

To convert from a string to object, for example, is purely a compile-time matter. Nothing needs to be done to the bits representing the string, in order to turn it into an object. After all, every string is an object.

Because of this, converting from IEnumerable<string> to IEnumerable<object> is trivial too - the runtime does not need to do anything.

This is not true for the int-to-object conversion, however. Because int is a value type and object is a reference type. to maintain these semantics, the runtime needs to create a reference type wrapper around that int in order to do the conversion, "boxing" it.

This means that the runtime cannot just "do nothing" if it wants to convert a IEnumerable<int> to IEnumerable<object>. You would expect to get reference types out of that IEnumerable, not value types, so when and how does the "boxing" happen? When and how should this happen in general, not just in the case of IEnumerable<T>?

I'm sure this in theory can all be figured out, designed, and implemented, but it would certainly not be as trivial as the case with implicit reference conversions. In fact, I reckon it would be a lot more complicated and very drastic changes to both the language and runtime would be needed, which is probably why this is not implemented.

Sweeper
  • 213,210
  • 22
  • 193
  • 313