views:

333

answers:

4

I am using a hierarchy of generic collection classes that derive from an abstract base class to store entity items that also derive from an abstract base class:

abstract class ItemBase { }

class MyItem : ItemBase
{
    public MyItem()
    {
    }
}


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

class MyCollection : CollectionBase<MyItem> { }

The objectives of this model are to enforce strict type discipline on the members of any class derived from CollectionBase<T> and to guarantee that these members have a default public constructor. Thus far it works.

Now I want to create a factory method that returns an instance of a class derived from CollectionBase<T>. I understand that the usual approach would be:

public CollectionBase<T> CreateCollection<T>();

... or maybe ...

public T Create CreateCollection<T>();

However, the problem is that the calling routine does not know what "T" is required. It is the factory method itself that must determine the specific type of collection to return. So I need a non-generic method signature that says "the return type will derive from CollectionBase<T>". I envisage something like this (if it were legal) ...

public CollectionBase<> CreateCollection();

I assume this is another of those thorny generic variance issues, but even after reading Eric Lippert's extensive explanation on the subject, I am still unclear whether what I am trying to do is likely to be feasible in C# 4.0, and whether there is a simple workaround in C# 3.0.

Thanks in advance for your ideas.

A: 

Probably what you need is:

public CollectionBase<Object> CreateCollection();

Since every class directly or indirectly inherits from the Object class.

quosoo
but that is hardly going to ensure any type safety, right?
Mitch Wheat
@Mitch Wheat: My read of the question was that he couldn't ensure type safety in this function.
T.J. Crowder
@Mitch Wheat You're right, but the purpose is to have "collection of anything", so the price is to have explicit type checks wherever required.
quosoo
Unfortunately, aside from the type safety issue, it doesn't work, because "object" doesn't ensure that subclasses will meet the default constructor rule, therefore the compiler rejects it.
Tim Coulter
A: 

I haven't actually done this, and apologies if it's completely naive, but...

public CollectionBase<Object> CreateCollection();

?

T.J. Crowder
A: 

If you really want it to be anything, you could just return ICollection, and then your consumers could call the .Cast<ItemBase>() extension method as they need.

From what I understand, in .NET 4.0 you would be able to return CollectionBase<ItemBase>, but I haven't tried it on the Beta yet.

Dave Markle
+1  A: 

Let's say the factory method were creating a single item. You would have:

public ItemBase CreateItem();

You have to use ItemBase because you can't know anything more specific. Applying that principle to the collection method, you get:

public CollectionBase<ItemBase> CreateCollection();

Due to lack of variance, you need an adapter class which derives from CollectionBase<ItemBase> and wraps the actual collection which is created.

In C# 4.0 you would just return the actual collection.

Edit: The factory might look like this, with the adapter embedded:

public class CollectionFactory
{
    public CollectionBase<ItemBase> CreateCollection()
    {
        if(someCondition)
        {
            return CreateAdapter(new MyCollection());
        }
        else
        {
            return CreateAdapter(new MyOtherCollection());
        }
    }

    private static CollectionAdapter<T> CreateAdapter<T>(CollectionBase<T> collection) where T : ItemBase, new()
    {
        return new CollectionAdapter<T>(collection);
    }

    private class CollectionAdapter<T> : CollectionBase<ItemBase> where T : ItemBase, new()
    {
        private CollectionBase<T> _collection;

        internal CollectionAdapter(CollectionBase<T> collection)
        {
            _collection = collection;
        }

        // Implement CollectionBase API by passing through to _collection
    }
}
Bryan Watts
Yes, this idea is looking promising. But I don't see how to create the wrapper class without the need for T.
Tim Coulter
Updated to include a possible implementation of the factory. The `CreateAdapter` method infers the item type from whatever collection you instantiate.
Bryan Watts
It was a good try, but the compile still doesn't like it ... "ItemBase must be a non-abstract type with a public parameterless constructor in order to use it as a parameter in the generic type or method CollectionBase<T>"
Tim Coulter
Ah, right, you can't create instances of an abstract class. Makes sense, but it complicates your problem. I'm not sure there is a type-safe way to accomplish what you need if you must have both the `new()` constraint and and the abstract `ItemBase`.
Bryan Watts
Thanks anyway. Your answer was innovative and I have implemented it by removing the new() constraint (and replacing it by a runtime check) until I can properly achieve what I am seeking in C# 4.0.
Tim Coulter
I think you would run into the same problem in C# 4.0 actually. At no point is the compiler going to allow you to declare CollectionBase<ItemBase>, even if you could implicitly cast to it via variance.
Bryan Watts