Reputation: 24919
Is it possible to cast a List<Subclass>
to List<Superclass>
in C# 4.0?
Something along these lines:
class joe : human {}
List<joe> joes = GetJoes();
List<human> humanJoes = joes;
Isn't this what covariance is for?
if you can do:
human h = joe1 as human;
why shouldn't you be able to do
List<human> humans = joes as List<human>;
than it wouldn't be legal to do (joe)humans[0] because that item has been down casted.. and everyone would be happy. Now the only alternative is to create a new List
Upvotes: 19
Views: 2619
Reputation: 12090
If I allow your List<Joe> joes
to be generalized as ...
List<Human> humans = joes;
... the two references humans
and joes
are, now onward, pointing to the exact same list. The code following the above assignment has no way of preventing an insertion/addition of an instance of another type of human , say a Plumber, into the list. Given that class Plumber: Human {}
humans.Add(new Plumber()); // Add() now accepts any Human not just a Joe
the list that humans
refers to now contains both joes and plumbers. Note that the same list object is still referred to by the reference joes
. Now if I use the reference joes
to read from the list object I might pop out a plumber instead of a joe. Plumber and Joe are not known to be implicitly interconvertable... so my getting of a plumber instead of a joe from the list breaks down type safety. A plumber is certainly not welcome through a reference to a list of joes.
However in the recent versions of C# , its kind of possible to work around this limitation for a generic class/collection by implementing a generic interface whose type parameter has an out
modifier on it. Say we now have ABag<T> : ICovariable<out T>
. The out modifier restricts the T to ouput positions only (e.g. method return types). You cannot enter any T into the bag. You can only read them out of it. This allows us to generalize joes to an ICovariable<Human>
without worrying about inserting a Plumber into it as the interface doesnt allow that. We can now write ...
ICovariable<Human> humans = joes ; // now its good !
humans.Add(new Plumber()); // error
Upvotes: 0
Reputation: 1500485
You can't do this, because it wouldn't be safe. Consider:
List<Joe> joes = GetJoes();
List<Human> humanJoes = joes;
humanJoes.Clear();
humanJoes.Add(new Fred());
Joe joe = joes[0];
Clearly the last line (if not an earlier one) has to fail - as a Fred
isn't a Joe
. The invariance of List<T>
prevents this mistake at compile time instead of execution time.
Upvotes: 26
Reputation: 17186
No. As Jared said, the co/contravariance features of C# 4.0 only support interfaces and delegates. However it doesn't work with IList<T>
either, and the reason is that IList<T>
contains methods to add and change items in the list -- as Jon Skeet's new answer says.
The only way to be able to cast a list of "joe" to "human" is if the interface is purely read-only by design, something like this:
public interface IListReader<out T> : IEnumerable<T>
{
T this[int index] { get; }
int Count { get; }
}
Even a Contains(T item)
method would not be allowed, because when you cast IListReader<joe>
to IListReader<human>
, there is no Contains(human item)
method in IListReader<joe>
.
You could "force" a cast from IList<joe>
to IListReader<joe>
, IListReader<human>
or even IList<human>
using a GoInterface. But if the list is small enough to copy, a simpler solution is to just copy it into a new List<human>
, as Paw pointed out.
Upvotes: 1
Reputation: 754715
No. The co/contravariance features of C# 4.0 only support interfaces and delegates. The do not support concrete types like List<T>
.
Upvotes: 2
Reputation: 2752
Instantiate a new human-list that takes the joes as input:
List<human> humanJoes = new List<human>(joes);
Upvotes: 6