Currently I'm preparing a presentation of the new generic variance features in C# for my colleagues. To cut the story short I wrote following lines:
IList<Form> formsList = new List<Form> { new Form(), new Form() };
IList<Control> controlsList = formsList;
Yes, this is of course not possible, as IList(Of T) is invariant (at least my thought). The compiler tells me that:
Cannot implicitly convert type
System.Collections.Generic.IList<System.Windows.Forms.Form>
toSystem.Collections.Generic.IList<System.Windows.Forms.Control>
. An explicit conversion exists (are you missing a cast?)
Hm, does this mean I can force an explicit conversion? I just tried it:
IList<Form> formsList = new List<Form> { new Form(), new Form() };
IList<Control> controlsList = (IList<Control>)formsList;
And… it compiles! Does it mean I can cast the invariance away? - At least the compiler is ok with that, but I just turned the former compile time error to a run time error:
Unable to cast object of type 'System.Collections.Generic.List`1[System.Windows.Forms.Form]' to type 'System.Collections.Generic.IList`1[System.Windows.Forms.Control]'.
My question(s): Why can I cast the invariance of IList<T>
(or any other invariant interface as to my experiments) away? Do I really cast the invariance away, or what kind of conversion happens here (as IList(Of Form)
and IList(Of Control)
are completely unrelated)? Is this a dark corner of C# I didn't know?
Essentially, a type could implement
IList<Control>
as well asIList<Form>
so it's possible for the cast to succeed - so the compiler lets it through for the time being (aside: it could potentially be smarter here and produce a warning because it knows the concrete type of the referenced object, but doesn't. I don't think it would be appropriate to produce a compiler-error since it is not a breaking change for a type to implement a new interface).As an example of such a type:
public class EvilList : IList<Form>, IList<Control> { ... }
What happens at run-time is just a CLR type-check. The exception you are seeing is representative of a failure of this operation.
The IL generated for the cast is:
From MSDN: