Levi Botelho
Levi Botelho

Reputation: 25214

List, array and IEnumerable covariance

I'll start with several postulates to better explain the context of my question:

Array Covariance

Postulate 1.1

An array of a value type is not covariant. int[] cannot pass for object[].

Postulate 1.2

An array of a reference type is covariant with a valid IEnumerable. string[] can pass for IEnumerable<object>).

Postulate 1.3

An array of a reference type is covariant with a valid covariant array. string[] can pass for object[].

List Covariance

Postulate 2.1 (same as 1.1)

A list of a value type is not covariant. List<int> cannot pass for List<object>.

Postulate 2.2 (same as 1.2)

A list of a reference type is covariant with a valid IEnumerable. List<string> can pass for IEnumerable<object>).

Postulate 2.3 (different from 1.3)

A list of a reference type is not covariant with a valid covariant List. List<string> cannot pass for List<object>).


My question concerns postulates 1.3, 2.2 and 2.3. Specifically:

  1. Why can string[] pass for object[], but List<string> not for List<object>?
  2. Why can List<string> pass for IEnumerable<object> but not for List<object>?

Upvotes: 6

Views: 1465

Answers (2)

supercat
supercat

Reputation: 81217

Arrays are covariant, but a System.Int32[] does not hold references to things which are derived from System.Object. Within the .NET runtime, each value-type definition actually defines two kinds of things: a heap object type and a value (storage location) type. The heap object type is derived from System.Object; the storage location type is implicitly convertible to the heap object type (which in turn derives from System.Object) but does not itself actually derive from System.Object nor anything else. Although all arrays, including System.Int32[] are heap-object types, the individual elements of a System.Int32[] are instances of the storage location type.

The reason that a String[] can be passed to code expecting an Object[] is that the former contains "references to heap-object instances of type derived from type String", and the latter likewise for type Object. Since String derives from Object, a reference to a heap-object of a type derived from String will also be a reference to a heap object which derives from Object, and a String[] will contain references to heap objects which derive from Object--exactly what code would expect to read from an Object[]. By contrast, because an int[] [i.e. System.Int32[]] does not contain references to heap-object instances of type Int32, its contents will not conform to the expectations of code which is expecting Object[].

Upvotes: -1

Lee
Lee

Reputation: 144196

List covariance is unsafe:

List<string> strings = new List<string> { "a", "b", "c" };
List<object> objects = strings;
objects.Add(1);              //

Array covariance is also unsafe for the same reason:

string[] strings = new[] { "a", "b", "c" };
object[] objects = strings;
objects[0] = 1;              //throws ArrayTypeMismatchException

array covariance in C# is recognised as a mistake, and has been present since version 1.

Since the collection cannot be modified through the IEnumerable<T> interface, it is safe to type a List<string> as an IEnumerable<object>.

Upvotes: 13

Related Questions