Reputation: 589
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
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
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
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