C#泛型列表的多类型泛型列表

12

以下是我问题的抽象和简化:

我有一组玩具和相应的盒子。我希望用户能够指定盒子可以容纳的最大玩具类型:

public class Box<T> {}

在Box类中,我希望有一个通用的玩具列表,但是盒子中包含的每个玩具都将具有通用类型:

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

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

Toys类将如下所示:
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...]
}

我希望能够创建不同类型的玩具,并将它们插入到指定类型的盒子中。然后,我想要能够将盒子添加在一起,返回一个具有最大类型的盒子。
我真的不知道该如何开始。具有多个类型的通用类列表真的让我感到困惑。我阅读了各种关于使用抽象类或接口的文章,但没有找到任何与我尝试做的事情类似的示例或内容。
如果有人可以提供任何帮助,我将非常感激。
解决方案可以使用C# 4.0。
可能需要进一步澄清:
我希望Toy是通用的,并在实例化时接受参数,因为Toy必须还有一个List作为成员。
Toy内部嵌套的List是我的主要问题。然后,我希望Box内有一个包含Toys的列表,但每个玩具都具有不同类型的构造函数。
更新:
我已经修复了Box to Box这个错误拼写。
更新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

最终:我最终取消了Box必须是通用的要求。然后,我使用反射来创建动态类型的Toy。谢谢大家。

公共列表<Toys> = 新列表<Toys>(); 公共列表<T> = 新列表<T>(); 这些不会编译,变量名被省略了。 "Toy还必须有一个List作为成员。":这个List中放什么,它的目的是什么?建议您重新编写问题描述,以更清楚地表达您的目标和意图:也许区分一个只容纳一个玩具的'ToyBox和一个容纳一个或多个'ToyBox对象的BoxOfToys? - BillW
我同意BillW在列表问题上的观点,并希望进一步澄清Toy实际上添加了哪些通用类型。对我来说,“Ball”和“Horse”已经像玩具一样了,那么“Toy<Ball>”或“Toy<Horse>”是什么呢?Horse和Ball有一个共同的基类吗? - TToni
6个回答

27
你正在构建的代码最好能够很好地模拟现实。如果要建模“B的A”,请使用泛型。可以容纳一种东西的箱子的一组种类将被建模为`Box`。只能容纳玩具的箱子将被建模为`Box`。 可以容纳一种东西,而且这个东西必须是玩具的一组种类将成为`Box where T : Toy`。
到目前为止还不错。但是,“Toy”这个概念在现实生活中没有对应的东西。您可能有一盒饼干或一盒玩具,但您没有饼干的玩具、洋娃娃的玩具或长颈鹿的玩具。 “toy of”的概念毫无意义,因此不要进行建模。
更明智的建模方法是:“存在一类名为玩具的物品。没有一个东西仅仅是玩具;每个玩具都是更具体的一种玩具。球是一种玩具。洋娃娃是一种玩具。” 所以进行如下建模:
abstract class Toy {}
class Doll : Toy {}
class Ball : Toy {}

您说:

我希望Toy是通用的,并且在实例化时接受参数,因为Toy还必须有一个列表作为成员。

为什么?玩具没有一堆东西。所以不要这样建模。相反,盒子在逻辑上被建模为盒子内部的玩具列表。(或者,由于盒子通常不适用排序,并且盒子仅包含唯一的玩具,因此也许更好的是将其建模为玩具的集合。)

我想能够创建许多不同类型的玩具,然后将它们插入到具有另一指定类型的Box中。

好的。所以对Box<T>进行的操作是void Insert(T item)。你可以把一个玩具放进一个玩具盒里,你可以把一个娃娃放进一个娃娃盒里,但是你不能把一个球放进一个娃娃盒里。

然后,我想能够将盒子添加在一起,返回具有最大类型的Box。

您需要更仔细地定义“最大类型”。如果您将一个娃娃盒放入一个球盒中,显然结果既不是球盒也不是娃娃盒。结果是一个玩具盒。

这是我如何建模的。我们已经有了玩具层次结构。我会继续说,T的盒子被实现为其内容的集合,并提供其内容的序列。

// 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(); }

到目前为止,都很无聊。现在我们来到有趣的部分。这只在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;
    }
}

现在你可以说
Box<Doll> dollbox = new Box<Doll>() { new Doll() };
Box<Ball> ballbox = new Box<Ball>() { new Ball() };
Box<Toy> toybox2 = Box<Toy>.MergeBoxes(ballbox, dollbox);

一个娃娃盒和一个球盒合并的结果是一个玩具盒。
这个最后一部分之所以有效,是因为在C# 4中,IEnumerable<T>是协变的。在C# 3中,要想正确实现这个功能会更加棘手;您需要做类似以下的事情:
Box<Toy> toybox2 = Box<Toy>.MergeBoxes(ballbox.Cast<Toy>(), dollbox.Cast<Toy>());

这有意义吗?


这很有道理。合并部分非常出色。但是,每个玩具都必须有一个类型为T的列表。将类型T视为此示例中的材料。我可能有一个塑料玩具,其中包含用于构建的塑料零件列表/数组。[请参见原帖中的更新2] - Chris
1
Eric是绝对正确的;你坚持认为必须作为规范的那个东西,恰恰是不合理的。如果一个“玩具”必须“包含”一系列零件(这本身就很可疑,因为这应该是一种引用关系,而不是包含关系),那么在抽象的“Toy”基类中放置一个“List<Material>”,或者更好的方法是有一个像抽象的“GetRequiredMaterials()”方法。没有理由将其作为通用类型参数 - 如果似乎需要它,则表示模型存在问题。 - Aaronaught
2
他在一开始就说这个例子是对他的问题进行抽象和简化。因此,他真正的问题很可能与玩具和盒子无关,而是完全不同的事情。这只是一个(选择不太好的)例子。 - TToni

1

我在构建一个泛型类型列表时遇到了问题,因为我不知道编译时的泛型部分,它们需要以某种方式动态生成。

我通过将泛型类设计成如下形式来解决了我的问题:

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

然后按照以下方式实例化和使用该类;

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() });

警告: 您可以访问子项的成员,但它们在列表中并不相同!这样做可以让您编译它,但在运行时可能会出错!

例如,我在我的类“EvenOtherType”上有一个“DoSomething()”方法。如果我在循环列表时尝试调用它,显然字符串和整数版本将在运行时崩溃!

请小心!


1

我不确定这是否是您真正想要做的事情,因为它有点 hackish。如果您想要每种可能的玩具类型列表,包括Box可能不知道的玩具类型,那么您需要使用通用参数来过滤出正确的玩具。除非Box只是一个填充常见物品的空白板(例如 BlackBoard),否则我不建议这样做。

例如,您可以只有一个List<Object>,并且所有方法在操作之前检查内容是否属于正确的类型。您也可以使用Dictionary<Type,Object>,以便跳过逐项筛选步骤,但会导致继承方面的一些问题(动物列表将不包括添加到长颈鹿列表中的东西)。

下面是Dictionary方法的简单实现:

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)];
    }
}

您可以调整此代码以限制列表为特定类型,并进一步调整它,以便您可以连接共享公共基础类型的列表。可能看起来像这样:

MultiGenericList<T> Join<C1, C2, T>(MultiGenericList<C1> child1, 
                                    MultiGenericList<C2> child2) where C1 : T, C2 : T
{
    ...
}

1
这是一个有趣的想法。是的,它有点hacky :) - Chris

1

我引用MSDN的话:

泛型类封装了不特定于特定数据类型的操作。最常见的用途是与集合一起使用,如链表、哈希表、栈、队列、树等。在这些集合中,添加和删除项等操作无论存储的数据类型如何,都以大致相同的方式执行。

根据您的描述,最接近适用于此的对象是Box,但同时您知道它将包含Toys,并有一个最大的ToySize,而Toys具有类型(球,马...)。我可能错了,但使用泛型可能并不真正必要。我想到的是:

    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; }
    }

除非你有使用泛型的特定原因。


0
public interface IToy {}

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

您可以通过反射访问Toy的每个成员(变量、方法等)。

如果您定义了Getter和Setter,只要它们不使用通用类型T,您就可以在接口中直接使用它们。

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

这可能是一个在CPU方面较慢的解决方案,但您可以立即访问每个通用类型。如果您要访问的类型数量较少,应该使用继承,使T由具体类型(例如您示例中的材料)泛化。


0
你最后一句话“每个玩具都有不同的构造函数”意味着你需要一个玩具基类,每个特定的玩具都从该类继承:
public abstract class Toy{} // abstract base class

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

或者定义一个通用的玩具接口,每种特定类型的玩具都实现该接口... 无论是接口还是基类都可以定义通用列表。

你需要这样做的原因是,如果你使用一个单一的类来处理所有类型的玩具,那么你将无法根据类型来修改构造函数... 这不是泛型支持的行为。


我应该表述得更清楚。我是指像 Toy<Ball> 和 Toy<Horse> 这样的不同类型构造函数。如果可能的话,我真的想避免多个类都继承自 Toy。 - Chris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接