Question

I have a class, Container<T>, which has a ContainerContents<T>. The Container actually takes two type constraint parameters Container<TContainer,TContents> - TContainer being the type of the container, and TContents being the type of contents it accepts.

I want to ensure that if TContainer is X or derived from X, then TContents will also be X or derived from X, but that TContents does not have to equal TContainer.

I'm trying to express the following kinds of things.

  • Things that can be carried around (Swag), like a pencil.
  • Things that cannot be carried (BaseObject), like a tree.
  • Things that can hold other things (Container)
  • Containers that cannot be carried, like a bank vault.
  • Carriable Containers (like a backpack).

If a container can be carried, then its contents must be carriable too. But, just because the Container is a backpack doesn't mean it can only carry backpacks.

I want to be able to code:

  • var ringWorld = new Container<BigRing, CivicWork>();
  • var pickleKnox = new Container<BankVault, Pickle>();
  • var swagBag = new Container<ToteBag, Swag>();
  • var tomeBag = new Container<ToteBag, Book>();

but not var treeBag = new Container<Bag, Tree>();

Here's my skeletal setup.

public abstract class BaseObject
{
    private readonly string _name;

    protected BaseObject(string name)
    {
        _name = name;
    }

    public string Name { get { return _name; } }
}
public class Swag : BaseObject
{
    private readonly int _weight;

    public Swag(string name, int weight):base(name)
    {
        _weight = weight;
    }

    public int Weight { get { return _weight; } }
}
/* I like the flexibility of i.e.: Container<BankVault,Pickles> 
but if the container itself is carriable (Swag), then its contents 
are by nature also carriable. */

public class Container<TContainer,TContents> : BaseObject 
    where TContainer:BaseObject 
    where TContents:BaseObject, or Swag if TContainer:(Swag or derived from Swag)
{
    ContainerContents<TContents> _contents;

    public Container(string name, int maxItems):base(name)
    {

        /* if (TContainer is derived from Swag) { TContents must be too } */

        _contents = new ContainerContents<TContents>(maxItems);
    }
}
public class ContainerContents<T> : List<T> where T : BaseObject
{
    int _maxItems;

    public ContainerContents(int maxItems)
    {
        _maxItems = maxItems;
    }
}
Was it helpful?

Solution

I don't think this is going to work.

I would create the following interfaces:

interface ICarryable { }
interface IContainer<T> { }

Then you could implement the following classes:

class Backpack<T> : ICarryable, IContainer<T>
where T : ICarryable 
{  }

class Vault<T> : IContainer<T>
{  }

If a class implements ICarryable, it can be carried. If it doesn't implement that interface, it is a fixed object that can't be carried. This describes more precisely, what is going on. Your generic Container class doesn't communicate, that the container is of type TContainer and its contents are of type TContainerContents.

To avoid violating the DRY principle, you can still create a generic container base class, your vault and backpack inherit from. Making it abstract ensures, that no one uses it instead of your concrete implementations.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top