Vasaka
Vasaka

Reputation: 589

Generics type casting

I just don't get something in the .NET generic type casting. Can someone explain what happens in the following code snippet?

void Main()
{
    IEnumerable<int> ints = new List<int>();
    IEnumerable<string> strings = new List<string>();

    var rez1=(IEnumerable<object>)ints; //runtime error
    var rez2=(IEnumerable<object>)strings; //works
    var rez3=(List<object>)strings; //runtime error
}

Upvotes: 8

Views: 355

Answers (3)

supercat
supercat

Reputation: 81347

The definition of every type which derives from System.ValueType, with the exception of System.Enum, actually defines two kinds of things: a heap object type, and a storage-location type. Instances of the latter may be implicitly converted to the former (making a copy of the data contained therein), and instances of the former may be explicitly typecast to the latter (likewise); even though both kinds of things are described by the same System.Type, and although they have the same members, they behave very differently.

A List<AnyClassType> will expect to hold a bunch of heap-object references; whether the list in question is a List<String>, List<StringBuilder>, List<Button>, or whatever, may be of interest to users of the list, but isn't really of interest to the List<T> itself. If one casts a List<Button> to an IEnumerable<Control>, someone who calls its GetEnumerator() method will expect to get an object which will output references to heap objects that derive from Control; the return from List<Button>.GetEnumerator() will satisfy that expectation. By contrast, if someone were to cast a List<Int32> to List<Object>, someone who called GetEnumerator() would expect something that would output heap object references, but List<Integer>.GetEnumerator will instead yield something that outputs value-type integers.

It's possible to store Int32 values into a List<Object> or a List<ValueType>; storing an integer to such a list will convert it to its heap object form and store a reference to it; calling GetEnumerator() would yield something that outputs heap references. There is no way to specify, however, that such a list will only contain instances of the heap type corresponding to Int32. In C++/CLI, it's possible to declare variables of type "reference to heap-stored valuetype", but the mechanisms behind generic types in .net cannot work with such types.

Upvotes: 0

phoog
phoog

Reputation: 43076

This is because interface covariance only works with reference types. Int32, of course, is a value type.

This gives more information: http://blogs.msdn.com/b/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx

And so does this: http://ericlippert.com/2011/09/19/inheritance-and-representation/

Upvotes: 2

Jon
Jon

Reputation: 437904

Let's start with the second line which is easiest.

That cast works because the type parameter of IEnumerable<T> is now covariant (that's what the out in out T does). This means you can cast an IEnumerable<Derived> to an IEnumerable<Base> freely.

The first line, which would seem to be the same case, does not work because int is a value type. Interface variance does not work with value types at all because value types do not really inherit from System.Object; they can be boxed into an object, but that's not the same. The documentation mentions that

Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type.

Finally, the third line does not work because the type parameter of List<T> is invariant. You can see there is no out on its type parameter; the rules disallow that because List<T> is not an interface:

In the .NET Framework 4, variant type parameters are restricted to generic interface and generic delegate types.

Upvotes: 11

Related Questions