Reputation: 4594
Given an interface IQuestion
and an implementation of that interface AMQuestion
, suppose the following example:
List<AMQuestion> typed = new List<AMQuestion>();
IList<IQuestion> nonTyped = typed;
This example yields, as expected, a compile error saying the two are not of the same type. But it states an explicit conversion exists. So I change it to look like this:
List<AMQuestion> typed = new List<AMQuestion>();
IList<IQuestion> nonTyped = typed as IList<IQuestion>;
Which then compiles but, at run time, nonTyped
is always null. If someone could explain two things:
It would be greatly appreciated. Thank you!
Upvotes: 22
Views: 1552
Reputation: 112334
A type with a generic type parameter can only be covariant if this generic type occurs only in read accesses and contravariant, if it occurs only in write accesses. IList<T>
allows both, read and write access to values of type T
, so it cannot be variant!
Let's assume that you were allowed to assign a List<AMQuestion>
to a variable of type IList<IQuestion>
. Now let’s implement a class XYQuestion : IQuestion
and insert a value of that type into our IList<IQuestion>
, which seems perfectly legal. This list still references a List<AMQuestion>
, but we cannot insert a XYQuestion
into a List<AMQuestion>
! Therefore the two list types are not assignment compatible.
IList<IQuestion> list = new List<AMQuestion>(); // Not allowed!
list.Add(new XYQuestion()); // Uuups!
Upvotes: 2
Reputation: 21917
The fact that AMQuestion
implements the IQuestion
interface does not translate into List<AMQuestion>
deriving from List<IQuestion>
.
Because this cast is illegal, your as
operator returns null
.
You must cast each item individually as such:
IList<IQuestion> nonTyped = typed.Cast<IQuestion>().ToList();
Regarding your comment, consider the following code, with the usual cliché animal examples:
//Lizard and Donkey inherit from Animal
List<Lizard> lizards = new List<Lizard> { new Lizard() };
List<Donkey> donkeys = new List<Donkey> { new Donkey() };
List<Animal> animals = lizards as List<Animal>; //let's pretend this doesn't return null
animals.Add(new Donkey()); //Reality unravels!
if we were allowed to cast List<Lizard>
to a List<Animal>
, then we could theoretically add a new Donkey
to that list, which would break inheritance.
Upvotes: 32
Reputation: 81149
Because List<T>
is not a sealed class, it would be possible for a type to exist which would inherit from List<AMQuestion>
and implement IList<IQuestion>
. Unless you implement such a type yourself, it's extremely unlikely that one will ever actually exist. Nonetheless, it would be perfectly legitimate to say, e.g.
class SillyList : List<AMQuestion>, IList<IQuestion> { ... }
and explicitly implement all the type-specific members of IList<IQuestion>
. It would thus also be perfectly legitimate to say "If this variable holds a reference to an instance of a type derived from List<AMQuestion>
, and if that instance's type also implements IList<IQuestion>
, convert the reference to the latter type.
Upvotes: 1
Reputation: 14716
The issue is that List<AMQuestion>
cannot be cast to IList<IQuestion>
, so using the as
operator does not help. Explicit conversion in this case means to cast AMQuestion
to IQuestion
:
IList<IQuestion> nonTyped = typed.Cast<IQuestion>.ToList();
By the way, you have the term "Covariance" in your title. In IList
the type is not covariant. This is exactly why the cast does not exist. The reason is that the IList
interface has T
in some parameteres and in some return values, so neither in
nor out
can be used for T
. (@Sneftel has a nice example to show why this cast is not allowed.)
If you only need to read from the list, you can use IEnumerable
instead:
IEnumerable<IQuestion> = typed;
This will work because IEnumerable<out T>
has out
defined, since you can't pass it a T
as parameter. You should usually make the weakest "promise" possible in your code to keep it extensible.
Upvotes: 7
Reputation: 6337
IList<T>
is not covariant for T
; it can't be, as the interface defines functions that take values of type T
in an "input" position. However, IEnumerable<T>
is covariant for T
. If you can limit your type to IEnumerable<T>
, you can do this:
List<AMQuestion> typed = new List<AMQuestion>();
IEnumerable<IQuestion> nonTyped = typed;
This does not do any conversions on the list.
The reason you cannot convert a List<AMQuestion>
to a List<IQuestion>
(assuming AMQuestion implements the interface) is that there would have to be several runtime checks on functions like List<T>.Add
, to make sure you were really adding an AMQuestion
.
Upvotes: 3
Reputation: 1700
The "as" operator will always return null there as no valid cast exists - this is defined behavior. You have to convert or cast the list like this:
IList<IQuestion> nonTyped = typed.Cast<IQuestion>().ToList();
Upvotes: 2
Reputation: 41474
Why it doesn't work: as
returns null
if the value's dynamic type cannot be casted to the target type, and List<AMQuestion>
cannot be casted to IList<IQuestion>
.
But why can't it? Well, check it:
List<AMQuestion> typed = new List<AMQuestion>();
IList<IQuestion> nonTyped = typed as IList<IQuestion>;
nonTyped.Add(new OTQuestion());
AMQuestion whaaaat = typed[0];
IList<IQuestion>
says "You can add any IQuestion
to me". But that's a promise it couldn't keep if it were a List<AMQuestion>
.
Now, if you didn't want to add anything, just view it as a collection of IQuestion
-compatible things, then the best thing to do would be to cast it to an IReadOnlyList<IQuestion>
with List.AsReadOnly
. Since a read-only list can't have strange things added to it, it can be casted properly.
Upvotes: 12