samus
samus

Reputation: 6202

Polymorphic Type Parameters in Generic Collections

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.

Upvotes: 15

Views: 5573

Answers (6)

poke
poke

Reputation: 388023

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.

Upvotes: 12

millimoose
millimoose

Reputation: 39970

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.

Upvotes: 12

mtzaldo
mtzaldo

Reputation: 105

You error is that B inherits from A; but List<B> don't inherits from List<A>. List<A> != A;

You can do this:

List<A> aL = new List<A>();
aL.Add(new B());

f (aL)

You can detect the type in void f(List<A> list)

foreach(A a in list)
{
  if (a is B)
    //Do B stuff
  else
    //Do A stuff
}

Upvotes: 1

Kenogu Labz
Kenogu Labz

Reputation: 1134

I think you may be looking for 'out' generic modifiers, which allow for covariance between two generic types.

http://msdn.microsoft.com/en-us/library/dd469487.aspx

An example as posted on that page:

// Covariant delegate. 
public delegate R DCovariant<out R>();

// Methods that match the delegate signature. 
public static Control SampleControl()
{ return new Control(); }

public static Button SampleButton()
{ return new Button(); }

public void Test()
{            
    // Instantiate the delegates with the methods.
    DCovariant<Control> dControl = SampleControl;
    DCovariant<Button> dButton = SampleButton;

    // You can assign dButton to dControl 
    // because the DCovariant delegate is covariant.
    dControl = dButton;

    // Invoke the delegate.
    dControl(); 
}

I'm not sure whether C# currently supports covariance for its current collections.

Upvotes: 1

Jon
Jon

Reputation: 437554

There's nothing inherently wrong with passing a collection of B to a method that expects a collection of A. However, there are many things that can go wrong depending on what you are going to do with the collection.

Consider:

void f(List<A> aL)
{
    aL.(new A()); // oops! what happens here?
}

Obviously there is a problem here: if aL were allowed to be a List<B> then this implementation would result in some type of runtime error, either on the spot or (much worse) if later on the code handles the A instance we put in as a B.

The compiler does not allow you to use a List<B> as a List<B> in order to preserve type safety and guarantee that your code will not need runtime checks to be correct. Note that this behavior is different than what (unfortunately) happens with arrays -- the language designer's decision is a tradeoff, and they decided differently on different occasions:

void f(A[] arr)
{
    arr[0] = new A(); // exception thrown at runtime
}

f(new B[1]);

Upvotes: 3

No Idea For Name
No Idea For Name

Reputation: 11597

your question is very similar to mine: the answer is that you can't do that cast because thoose are diferent types create by a template class and they do not inherit. what you can do is:

f(bL.Cast<A>());

Upvotes: 0

Related Questions