Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphic Type Parameters in Generic Collections

Tags:

c#

generics

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.

like image 943
samus Avatar asked Jul 30 '13 16:07

samus


People also ask

Can we use polymorphism in generics?

The polymorphism applies only to the 'base' type (type of the collection class) and NOT to the generics type.

What is generic parameter in C#?

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.


2 Answers

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 Bs 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.

like image 171
millimoose Avatar answered Nov 15 '22 14:11

millimoose


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.

like image 21
poke Avatar answered Nov 15 '22 16:11

poke