Why does the C# compiler not allow polymorphic type (T) parameters in generic collections (ie, List[T]) ?
Take class 'A' and 'B' for example, where 'B' is a subclass of 'A'
class A { }
class B : A { }
and consider a function that takes a list of type 'A'
void f(List<A> aL) { }
that gets called with a list of type 'B'
List<B> bL = new List<B>();
f(bL);
The following error is given
ERROR: cannot convert from List<B> to List<A>
What semantic rule is being violated ?
Also is there an "elegant" mean to this end, aside from looping through and casting each element (I want some sugar please) ? Thanks.
The polymorphism applies only to the 'base' type (type of the collection class) and NOT to the generics type.
Generic became part of C# with version 2.0 of the language and the CLR, or Common Language Runtime. It has introduced the concept of type parameters, which allow you to design classes and methods that defer the specification of one or more types until the class or method is declared and instantiated by client code.
List<B>
simply is not a subtype of List<A>
. (I'm never sure about what "covariant" and what "contravariant" is in this context so I'll stick with "subtype".) Consider the case where you do this:
void Fun(List<A> aa) {
aa(new A());
}
var bb = new List<B>();
Fun(bb); // whoopsie
If what you want to do was allowed it would be possible to add an A
to a list of B
s which is clearly not type-safe.
Now, clearly it's possible to read elements from the list safely, which is why C# lets you create covariant (i.e. "read-only") interfaces - which let the compiler know it's not possible to cause this sort of corruption through them. If you only need read access, for collections, the usual one is IEnumerable<T>
, so in your case you might just make the method:
void Fun(IEnumerable<A> aa) { ... }
and use the Enumerable
methods - most should be optimised if the underlying type is List
.
Unfortunately, because of how the C# generics stuff works, classes can't be variant at all, only interfaces. And as far as I know, all the collection interfaces "richer" than IEnumerable<T>
are "read-write". You could technically make your own covariant wrapper interface that only exposes the read operations you want.
Take this little example as to why this cannot work. Imagine we have another subtype C
of A
:
class A {}
class B : A {}
class C : A {}
Then obviously, I can put a C
object in a List<A>
list. But now imagine the following function taking an A-list:
public void DoSomething (List<A> list)
{
list.Add(new C());
}
If you pass a List<A>
it works as expected because C
is a valid type to put in a List<A>
, but if you pass a List<B>
, then you cannot put a C
into that list.
For the general problem that’s happening here, see covariance and contravariance for arrays.
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