Reputation: 516
I have an abstract Content
class and concrete subclasses
public abstract class Content
public class ContentA : Content
public class ContentB : Content
I also have an abstract generic ContentSource
class and concrete subclasses
public abstract class ContentSource<T> where T : Content
public class SourceX : ContentSource<ContentA>
public class SourceY : ContentSource<ContentB>
And I want to have a list of ContentSource<Content>
objects that are the subclasses of ContentSource
var ContentSources = new List<ContentSource<Content>>
{
new SourceX(),
new SourceY(),
};
But this doesn't compile - I get a 'Cannot convert from SourceX to ContentSource' error.
Why does this not work?
Upvotes: 9
Views: 1128
Reputation: 3693
While Kobi gives a perfect answer how you can get it to work, I will give you a simple answer why it does not work.
In your example, you want to use polymorphism to manage different types that derive from a common base class in a single list of type base class. Something along the lines→
public abstract class Base {}
public class DerivedFirst : Base {}
public class DerivedSecond : Base {}
var first = new DerivedFirst();
var second = new DerivedSecond();
var list = new List<Base>{first,second}
Which is perfectly fine as they derive from the common Base
class. Now let us take a look at your case. But before that you should understand the difference between an Open Type
and Closed Type
. Simply put any generic type without its type parameters are open types and can not be instantiated. List<>
is an open type
whereas List<int>
is a closed type
. When you create a closed type
it does not derive from its open definition. It is a completely new type on its own.
var i_am_false = typeof(List<int>).IsSubclassOf(typeof(List<>));//this is false
Here is your case. You define your classes which will be type arguments.
public abstract class Content
public class ContentA : Content
public class ContentB : Content
Here you define your sources
public abstract class ContentSource<T> where T : Content
public class SourceX : ContentSource<ContentA>
public class SourceY : ContentSource<ContentB>
Putting to words
You have SourceX
which derives from ContentSource<ContentA>
which derives from Object
You have SourceY
which derives from ContentSource<ContentB>
which derives from Object
and finally
You have var ContentSources = new List<ContentSource<Content>>
, which is a list of ContentSource<Content>
which is in no way connected to ContentSource<ContentA>
or ContentSource<ContentB>
. They simply have same implementation with respect to generic parameter.
In the end, let us call
ContentSource<Content>
class M
which derives from Object
ContentSource<ContentA>
class N
which derives from Object
ContentSource<ContentB>
class O
which derives from Object
and have a look at you are doing→
public class SourceX : N{}
public class SourceY : O{}
var ContentSources = new List<M>{
new SourceX(),
new SourceY(),
};
Of course it would not work :). Lastly why it works with covariance please have a look at Kobi's answer.
Upvotes: 3
Reputation: 138087
This can be achieved in C# using covariance, but you'd have to use an interface as the list type:
public interface IContentSource<out T> where T : Content {}
public class SourceX : IContentSource<ContentA> {}
public class SourceY : IContentSource<ContentB> {}
var ContentSources = new List<IContentSource<Content>>
{
new SourceX(),
new SourceY(),
};
Working example
This is explained nicely here: <out T>
vs <T>
in Generics
You can still use an abstract class, but the list would still have to be a list of the interface:
public interface IContentSource<out T> where T : Content {}
public abstract class ContentSource<T> : IContentSource<T> where T : Content {}
public class SourceX : ContentSource<ContentA> {}
public class SourceY : ContentSource<ContentB> {}
There is also a great explanation of why it isn't supported for classes: Why does C# (4.0) not allow co- and contravariance in generic class types?
Upvotes: 8