简体   繁体   中英

C# Generic List of Generic List of Multiple Types

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.

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?

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
{
    ...
}

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.

I had this problem when constructing a list of generic types that I didn't know the generic part of at compile time, they needed to be dynamic somehow.

I solved my problem by having the generic class like this:

public class MyGenericType<T>
{
    public T Data;
    public string SomeString;
}

And then instantiating and using that class as follows;

List<MyGenericType<dynamic>> myList = new List<MyGenericType<dynamic>>();
myList.Add(new MyGenericType<dynamic>(){ Data = "my string" });
myList.Add(new MyGenericType<dynamic>(){ Data = null });
myList.Add(new MyGenericType<dynamic>(){ Data = new EvenOtherType() });

WARNING: You can access the members of the children, but they're not the same on all in the list! It'll let you compile it, but it might break during runtime!

For example, I have a "DoSomething()" method on my class "EvenOtherType". If I try to call that while looping over the list, obviously the string and the int versions will crash during runtime!

Be careful!

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.

public interface IToy {}
public class Toy<T>: IToy
{
    public List<T> = new List<T>();
    public string Name;
    public string Color;
}

You can access every member (variable, method etc.) of the Toy via reflection.

Naturally if you define Getters and Setters, you can just use them in the interface as long as they don't use a generic type T.

List<IToy> toys = new List(IToy);
toys.Add(new Toy());
IToy toy = toys.get(0);
toy.GetType().GetField("Name").GetValue(toy);

This is probably a slower solution cpu-wise but you can access EVERY generic type right away. If the number of the types you want to access is low, you should use inheritance so that T is generalized by a concrete type (like material in your example).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM