Just came across this:
Func<List<object>> foo = () => new List<object>();
List<string> s = (List<string>)foo();
IList<string> s1 = (IList<string>)foo();
Compiler complains about casting to List (makes sense), but says nothing about IList. Makes me wonder why is that?
The compiler knows that a List<X>
cannot be a List<Y>
.
It therefore gives a compiler error.
However, the second cast could succeed if the List<X>
is actually some derived class that also implements IList<Y>
.
You will only get a compile-time error from a cast if neither type is an interface, or if one type is an unrelated interface and the other type is sealed (or a struct).
To quote the spec (§6.4.2)
The explicit reference conversions are:
- From object and dynamic to any other reference-type.
- From any class-type S to any class-type T, provided S is a base class of T.
- From any class-type S to any interface-type T, provided S is not sealed and provided S does not implement T.
- From any interface-type S to any class-type T, provided T is not sealed or provided T implements S.
- From any interface-type S to any interface-type T, provided S is not derived from T.
- [snip]
(emphasis added)
The provided...
clauses exclude conversions that are actually implicit.
An object that is known to be a List<object>
might implement IList<string>
as well as IList<object>
, so it's possible that the cast can succeed. It can't in this case because we know that the statement is simply new List<object>()
, but the compiler doesn't consider that. You might've extended List<T>
and implemented the other, e.g.
// not recommended, but second cast works
public class MyWeirdList : List<object>, IList<string>
An object that is known to be a List<object>
cannot possibly also be a List<string>
, because you can only inherit from a single type.
public class MyWeirdList : List<object>, List<string> // compiler error
If List<T>
were sealed, both casts would be invalid, because then the compiler would know for sure that the class couldn't implement IList<string>
. You can try this by using this class instead:
public sealed class SealedList<T> : List<T> { }
The first line fails at compile time, the second gives an "Unable to cast object of type 'System.Collections.Generic.List1[System.Object]' to type 'System.Collections.Generic.IList
1[System.String]'." exception during runtime.
If you look at this question (Cast IList<string> to IList<object> fails at runtime), the answer clarifies why this compile works and also provides an example for a class that could satisfy the conditions provided.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With