tags:

views:

617

answers:

4

Here is an abstraction and simplification of my issue:

I have a set of toys and a corresponding box for these toys. I want the user to be able to specify the largest type of toy that the box can hold:

public class Box<T> {}

then within the Box class I want to have a generic list of toys, but each toy contained within the box will have a generic type:

public class Box<T>
{
    public List<Toy> = new List<Toy>();
    public bool Whatever;

    [member functions, constructors...]
    [The member functions will depend on T]
}

The Toys class will look like this:

public class Toy<T> where T : struct //T is any type
{
    public List<T> = new List<T>();
    public string Name;
    public string Color;

    [member functions, constructors...]
}

I want to be able to create Toys with many different types and then insert them into a Box with another specified type. Then I'd like to be able to add boxes together returning a Box with the largest type.

I really don't know how to begin. The list of a generic class with multiple types is really throwing me for a loop. I read various articles about using an abstract class or an interface, but haven't found an example or anything that accomplishes something similar to what I'm trying to do.

Any assistance anybody could provide would be very appreciated.

The solution can be in C# 4.0.

Possible Future Clarification:

I want Toy to be generic and accept a argument at instantiation because Toy must also have a List as a member.

The nested List within Toy is my main problem. I then want a list within Box that holds Toys, but each toy has as different type constructor.

Update: I fixed the Box to Box that was a typo.

Update 2:

Toy<plastic> tyPlastic = new Toy<plastic>("Name1", Blue, new plastic[] {0xFFEE00, 0xF34684, 0xAA35B2});
Toy<wood> tyWood = new Toy<wood>("Name2", Grain, new wood[] {a1, f4, h7});

Box<plastic> bxBox = new Box<plastic>();//The Box has the ability to hold both plastic and wood toys.  Plastic > Wood > Paper

Final: I ended up removing the requirement for Box to be generic. I then used reflection to create dynamically typed Toy. Thanks everybody.

A: 

Your very last line "every toy has a different constructor" implies that you need either a Toy base-class, with each specific toy inheriting from that class:

public abstract class Toy{} // abstract base class

public class Ball : Toy
{
    public Ball(){} // Ball constructor
}

or an interface to define the generic Toy, with each specific type of Toy implementing the interface... either the interface or base class can define the generic list.

The reason you need this is that if you use a single class for all types of Toy then you won't be able to alter the constructor based on the type... that's just not a behavior generics support.

STW
I should have been more clear. I meant like Toy<Ball> and Toy<Horse> by separate type constructor.I'd really like to avoid multiple class all inheriting from Toy if possible.
Chris
A: 

I'm not sure if this is what you actually want to do, since it is a bit hackish. If you want lists of every conceivable toy type, including ones Box might not know about, you're going to need to use your generic parameters to filter out the correct toys. I don't recommend doing this unless the Box is just a blank slate for stuffing common things into (eg. a BlackBoard).

For example, you could have just one List<Object> and all the methods check if the contents are of the correct type before manipulating them. You could also use a Dictionary<Type, Object> in order to skip the term-by-term filtering step, but cause some problems with inheritance (the list of animals wouldn't include things added to the list of giraffes).

Here is a simple implementation of the Dictionary approach:

public class MultiGenericList
{
    private readonly Dictionary<Type, Object> map = new Dictionary<Type,Object>();

    public List<T> GetList<T>()
    {
        if (!this.map.ContainsKey(typeof(T))) this.map.Add(typeof(T), new List<T>());
        return (List<T>)this.map[typeof(T)];
    }
}

You can adapt this code to restrict the lists to be of a specific type, and you can adapt it further so you can join lists which share a common base type. That might look like this:

MultiGenericList<T> Join<C1, C2, T>(MultiGenericList<C1> child1, 
                                    MultiGenericList<C2> child2) where C1 : T, C2 : T
{
    ...
}
Strilanc
This is an interesting idea. And yes it is a bit hacky :)
Chris
+1  A: 

I quote MSDN:

Generic classes encapsulate operations that are not specific to a particular data type. The most common use for generic classes is with collections like linked lists, hash tables, stacks, queues, trees and so on where operations such as adding and removing items from the collection are performed in much the same way regardless of the type of data being stored.

Given your description, the closest object to which that would be applicable would be Box, but then again, you know its going to contain Toys, and has a maximum ToySize, and that Toys have a type (Ball, Horse...). I might be wrong here, but going with generics might not really be necessary. I'm thinking something like :

    public class Box
    {
        public ToyType MaxType { get; set; }
        public List<Toy> Toys = new List<Toy>();

        public void Add(Toy toy)
        {
            if (toy.Type.Size <= MaxType.Size) //if its not larger, or whatever compatibility test you want
                Toys.Add(toy);
        }
    }

    public class Toy
    {
        public ToyType Type { get; set; }
        public Toy(ToyType type)
        {
            Type = type;
        }  
    }

    public abstract class ToyType
    {
        public abstract string TypeName { get; set; }
        public abstract int Size { get; set; }
    }

Unless you have a particular reason behind using generics.

Dynami Le Savard
+8  A: 

The code you're building will be best understood if it models reality well.

The way to model "an A of B" is to use generics. A set of kinds of box that can hold one kind of thing would be modelled as Box<T>. A box that can only hold toys would be Box<Toy>. A set of kinds of box that can hold one kind of thing, and that thing has to be a toy would be a Box<T> where T : Toy.

So far so good. But the concept of Toy<T> doesn't map to anything in real life. You might have a box of biscuits or a box of toys, but you don't have a toy of biscuits, a toy of dolls or a toy of giraffes. The concept "toy of" doesn't make any sense, so don't model it.

A more sensible thing to model would be "there is a general class of things called toys. There is no one thing that is just a toy; every toy is a more specific kind of toy. A ball is a toy. A doll is a toy." So model that:

abstract class Toy {}
class Doll : Toy {}
class Ball : Toy {}

You said

I want Toy to be generic and accept a argument at instantiation because Toy must also have a List as a member.

Why? A toy does not have a list of things. So don't model that. Rather, a box is logically modelled as a list of the toys that are inside the box. (Or, since a box does not generally apply an ordering, and a box contains only unique toys, perhaps a set of toys would be better.)

I want to be able to create Toys with many different types and then insert them into a Box with another specified type.

OK. So an operation on Box<T> is void Insert(T item). You can put a toy into a box of toys, you can put a doll into a box of dolls, but you cannot put a ball into a box of dolls.

Then I'd like to be able to add boxes together returning a Box with the largest type.

You need to more carefully define "the largest type". If you add a box of dolls to a box of balls, clearly the result is neither a box of balls nor a box of dolls. The result is a box of toys.

Here's how I would model this. We already have the toy hierarchy. I would continue by saying that a box of T is implemented as a set of its contents, and provides a sequence of its contents.

// Haven't actually compiled this.
class Box<T> : IEnumerable<T>
{
    private HashSet<T> set = new HashSet<T>();
    public Insert(T item) { set.Add(item); }
    public IEnumerator<T> GetEnumerator() { return set.GetEnumerator(); }
    public IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }

All very boring so far. Now we come to the interesting bit. This only works well in C# 4.

    public static Box<T> MergeBoxes(IEnumerable<T> box1, IEnumerable<T> box2)
    {
        Box<T> box = new Box<T>();
        foreach(T item in box1) box.Insert(item);
        foreach(T item in box2) box.Insert(item);
        return box;
    }
}

Now you can say

Box<Doll> dollbox = new Box<Doll>() { new Doll() };
Box<Ball> ballbox = new Box<Ball>() { new Ball() };
Box<Toy> toybox2 = Box<Toy>.MergeBoxes(ballbox, dollbox);

The result of merging a box of dolls with a box of balls is a box of toys.

This last bit only works because IEnumerable<T> is covariant in C# 4. In C# 3, this would be trickier to get right; you'd have to do something like:

Box<Toy> toybox2 = Box<Toy>.MergeBoxes(ballbox.Cast<Toy>(), dollbox.Cast<Toy>());

Does that make sense?

Eric Lippert
This makes good sense. The merge part is excellent. But, each Toy MUST have a list of type T. Think of the type T is the material in this example. I might have a Toy that is plastic which contains a list/array of plastic parts need for construction. [See Update 2 in original post]
Chris
Eric is absolutely right; the thing that you're insisting *must* be part of the spec is exactly the thing that doesn't make sense as part of the spec. If a `Toy` must "contain" a list of parts (already dubious, as this should be a referential relationship, not containment), then put a `List<Material>` in the abstract `Toy` base class, or better yet, have something like an abstract `GetRequiredMaterials()` method. There's no reason to make it a generic type parameter - if it seems to be required then it would indicate a problem with the model.
Aaronaught
He says right at the beginning that the example is an abstraction and simplification of his issue. So there is a good possibility that his real problem doesn't involve Toys and Boxes, but something completely different. It's probably just a (not very well chosen) example.
TToni