Reputation: 6202
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
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
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 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.
Upvotes: 12
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
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
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
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