sean2000
sean2000

Reputation: 516

Cannot add a subclass of a generic class to a list

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

Answers (2)

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

Kobi
Kobi

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

Related Questions