2

I am trying to write a factory method that will create a derived instance of an abstract generic collection class. Here are the base classes ...

abstract class ItemBase { }

abstract class CollectionBase<T> : Collection<T> where T : ItemBase, new() { }

...and their derived classes ...

class Item : ItemBase { }

class ItemCollection : CollectionBase<Item> {}

Now, I want a factory method that will create an ItemCollection. But note that the derived classes Item and ItemCollection are unknown to the class that contains this factory method. This is how I imagine it should be ...

static T CreateItemCollection<T>() where T : CollectionBase<ItemBase>, new()
{
    return new T();
}

... and I imagine invoking it thus ...

var collection = CreateItemCollection<ItemCollection>();

But the factory method won't compile because ItemBase must have a parameterless constructor. And the invokation call refuses to believe that ItemCollection is derived from CollectionBase<ItemBase>.

Can someone please point me in the right direction? Thanks.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
Tim Coulter
  • 8,705
  • 11
  • 64
  • 95

2 Answers2

6

ItemCollection isn't derived from CollectionBase<ItemBase>, due to generic invariance. After all, you can add an ItemBase to a CollectionBase<ItemBase> - but you don't want that for your ItemCollection!

You need to make the method generic in two type parameters:

static T CreateItemCollection<TCollection, TItem>()
    where TCollection : CollectionBase<TItem>, new()
    where TItem : ItemBase
{
    return new TCollection();
}

Only the collection type needs a parameterless constructor. You'd call this with:

var collection = CreateItemCollection<ItemCollection, Item>();
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks. This solves my problem, even if I still can't fully understand why the compiler insists on being so strict (as commented by JaredPar below). – Tim Coulter Sep 08 '09 at 15:32
  • 1
    @Tim: As I said, because `ItemCollection` *must not* allow all the same calls as `CollectionBase`. Read up on Eric Lippert's blog series about variance for more details - unfortunately I need to run now, so don't have time to chase the link. – Jon Skeet Sep 08 '09 at 15:33
3

The problem here is generic constraints, in C# 3.0, have any leeway with regards to variance. The matching is instead fairly strict. Since ItemCollection derives from CollectionBase<Item> it is not considered to be derived from CollectionBase<ItemBase> even though the types may appear to be compatible.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454