泛型类型的集合

42
如果我有一个通用类:
public class MyClass<T> 
{
  public T Value;
}

我想实例化一些项目,例如...

new MyClass<string>
new MyClass<int>

我想把它们添加到一个集合中,但是如何定义这个集合以便它能够保存一系列的通用类型?然后我想在某个时候遍历这个集合,并使用 Value 属性。可行吗?


你是如何声明你的类的?你是如何定义支持集合的? - Oded
这只是一个“假设”,还是你试图将其作为解决方案的一部分使用?我之所以问这个问题,是因为如果我们有更多的上下文信息,我们可能能够提供更好的答案.. :) - Rob
相同的问题稍后被重新发布(我投票关闭,但可能无法成功,因为它太旧了):https://dev59.com/ZVDTa4cB1Zd3GeqPLKsx - Steven Jeuris
10个回答

41

将您的泛型类继承非泛型基类或实现非泛型接口。然后,您可以拥有此类型的集合并在访问集合内容的任何代码中进行转换。

这里是一个示例。

public abstract class MyClass
{
    public abstract Type Type { get; }
}

public class MyClass<T> : MyClass
{
    public override Type Type
    {
        get { return typeof(T); }
    }

    public T Value { get; set; }
}

// VERY basic illustration of how you might construct a collection
// of MyClass<T> objects.
public class MyClassCollection
{
    private Dictionary<Type, MyClass> _dictionary;

    public MyClassCollection()
    {
        _dictionary = new Dictionary<Type, MyClass>();
    }

    public void Put<T>(MyClass<T> item)
    {
        _dictionary[typeof(T)] = item;
    }

    public MyClass<T> Get<T>()
    {
        return _dictionary[typeof(T)] as MyClass<T>;
    }
}

1
返回类型是一个有趣的变化。 - Steven Sudit
1
你的解决方案确实很优雅。保持内部类型字典确实可以使访问方法更简单。 - Josh E
此外,您现在可以使用 dynamic 关键字:http://msdn.microsoft.com/zh-cn/library/dd264736.aspx - Tommy
7
在这种情况下,不能添加多个相同的T项。例如,集合中不能包含两个MyClass<string> - Steven Jeuris

8
我能想到的唯一方法是(为了测试,使用控制台应用程序进行封装):
class Program
{
    static void Main(string[] args)
    {
        var x = new MyClass<string>() { Value = "34" };
        var y = new MyClass<int>() { Value = 3 };

        var list = new List<IMyClass>();
        list.Add(x);
        list.Add(y);

        foreach (var item in list)
        {
            Console.WriteLine(item.GetValue);
        }
    }

    private interface IMyClass
    {
        object GetValue { get; }
    }

    private class MyClass<T> : IMyClass
    {
        public T Value;

        public object GetValue
        {
            get
            {
               return Value;
            }
        }
    }
}

即实现一个空接口,然后创建一个包含实现了该接口的类实例的集合。

更新: 我已经在接口中添加了一个“GetValue”方法,它允许您将MyClass实例的“Value”作为对象访问。如果您想拥有一个包含混合类型的集合,这是目前最好的方法了。


不错的选择 - 这是使用基类与我的答案等效的答案 :) - Josh E
但是你如何访问值属性? - Tim Coker
1
在这种情况下,你可以直接使用List<object>并结束了...(划掉,我重新阅读了一遍,意识到该接口至少提供了Value属性)。 - CodingWithSpike
1
@Jeremy - 我已经更新了我的答案,包括在接口中添加了一个“GetValue”方法,该方法返回MyClass的“Value”属性作为对象。据我所知,在3.5及以下版本中,这就是最好的了。在4.0中可能有一些奇怪的Co/Contra-Variance技巧,但我还没有探索过 :) - Rob
1
但是,正如rally25s所建议的那样,由于对存储数据的唯一访问是通过一个对象进行的,因此您可以将其存储为一个对象,并使其成为一个非泛型类。 - James Curran
显示剩余4条评论

4

您需要为MyClass定义一个基类,然后您的集合将是一个基类的列表。例如:

void Main()
{
 var a = new MyClass<string>();
 var b = new MyClass<int>();
 var c = new List<MyBase>();
 c.Add(a);
 c.Add(b);

}

public class MyBase { }
// Define other methods and classes here
public class MyClass<T> : MyBase {
public T Value { get; set;}
}

1
在您的示例中,将MyBase标记为抽象类可能是值得的,以使意图更清晰。 :) - Rob
有趣...我该如何遍历列表并检查Value属性的内容? - Jeremy
你需要遍历整个集合,然后在其中调用GetType().GetGenericParameters().First()来获取类型,接着提取Value作为一个对象并将其转换为之前提取的类型...这是很多工作,所以你可能需要考虑这个逻辑的目的,并查看是否有不同的方法来实现相同的结果。 - Josh E

4

我的大多数通用类型都有带有“未类型化”的成员的接口:

private interface IMyClass
{
    object UntypedValue { get; }
}

private class MyClass<T> : IMyClass
{
    T Value { get; set; }

    object UntypedValue { get { return Value; } }
}

您也可以通过显式接口实现来实现这一点,但在我看来,使用单独的名称更容易。 (也有一些关于显式接口实现的CA提示)


3

您希望拥有一个MyClass的集合,其中每个实例的类型参数T的值不同。在.NET中这是不可能的;它缺乏Java通配符(?)的等效物。相反,您需要创建一个非泛型的基类或接口,MyClass可以实现它。例如:

public interface IMyClass {
  object Value { get; set; }
}
public class MyClass<T> : IMyClass {
  public T Value { get; set; }
  object IMyClass.Value {
    get { return Value; }
    set { Value = (T)value; }
  }
}
List<IMyClass> m = new List<IMyClass> { new MyClass<string>(), new MyClass<int> };

@commongenius,你确定它不会编译吗?在我的VS2k8中,它可以很好地编译。 - Rob
当我发表了我的评论时,原始帖子已经被更改了。我相应地更改了我的评论。 - David Nelson

2
自 .Net 3 开始,就有一个 CompositeCollection 类,允许包含多个唯一的项甚至是集合。它被 WPF 开发人员用于在 Xaml 中存储和显示不同的项。但这并不意味着它不能在非 WPF 的情况下使用。
以下是一个示例,其中我从字符串到十进制数存储不同的东西,并提取和枚举所有项,然后是特定类型的项:
CompositeCollection cc = new CompositeCollection();

cc.Add(1);
cc.Add("Item One");
cc.Add(1.0M);
cc.Add(1.0);
cc.Add("Done");

Console.WriteLine ("Every Item");

foreach (var element in cc)
    Console.WriteLine ("\t" + element.ToString());

Console.WriteLine (Environment.NewLine + "Just Strings");

foreach (var str  in cc.OfType<string>())
    Console.WriteLine ("\t" + str);

/* Outputs:

Every Item
  1
  Item One
  1.0
  1
  Done

Just Strings
  Item One
  Done

*/

使用CompositeCollection相对于List<object>有什么价值? - Mike Christiansen
object的拆箱可能是任何东西,而CompositeCollection是一个类型安全的已知实现,它是一组ICollection/IList的事物。它还遵循INotifyCollectionChanged,而object当然可能不会。它的已知存在允许控件处理一个集合,这有助于绑定;主要用于WPF。 - ΩmegaMan
1
我在文档中没有看到任何指示它是类型安全集合的内容。它实现了IListIEnumerable,但没有实现IList<T>IEnumerable<T>。它的Add方法接受一个object。它的索引器接受并返回object。源代码显示它由一个ArrayList支持。CompositeCollection类型似乎只是一种特殊类型的ArrayList,具有WPF需要的特殊处理,例如实现INotifyCollectionChangedICollectionViewFactory - Mike Christiansen

2
我认为问题在于泛型类根本不是同一种类型。如果我理解正确的话,它们只是在编译时创建整个新类型的模板。因此,MyClass<int>MyClass<string>在运行时是完全不同的类型。它们就像MyIntClassMyStringClass一样,你显然不能在没有装箱的情况下将它们放在同一个列表中。它们不一定继承相同的基类、实现相同的接口或其他任何东西。它们与其他任何两种类型一样不同,你必须对待它们如此(即使你认为你知道得更好)。
当然,你可以让它们实现接口、继承基对象或使用其他已经给出的选项。请查看commongenius的答案,了解一种很好的方法。

只是提供一点信息:在 C# 中,泛型不仅仅是编译时的构造。实际上,在运行时使用它们时,运行时会动态创建类型。文档链接:https://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/generics/generics-in-the-run-time - Mike Christiansen

1

另一种方法是使用非泛型类,并传递一个处理程序来从内部执行操作。

interface IMyClass
{
    void ShowValues(MyHandler handler);
}

// Contains a List<string>.
class MyClassString : IMyClass
{
    private List<string> _list = new List<string> { "hello", "world" };

    public void ShowValues(MyHandler handler)
    {
        handler.ShowValues(_list);

    }
}

// Contains a List<int>.
class MyClassInt : IMyClass
{
    private List<int> _list = new List<int> { 1, 2 };

    public void ShowValues(MyHandler handler)
    {
        handler.ShowValues(_list);
    }
}

// Handler that has overloaded methods for all the supported types.
class MyHandler
{
    public void ShowValues(IEnumerable<string> list)
    {
        foreach (var item in list)
        {
            Console.WriteLine(item);
        }
    }

    public void ShowValues(IEnumerable<int> list)
    {
        foreach (var item in list)
        {
            Console.WriteLine(item);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var myClasses = new IMyClass[]
        {
            new MyClassString(),
            new MyClassInt(),
        };

        var myHandler = new MyHandler();

        foreach (var myClass in myClasses)
        {
            myClass.ShowValues(myHandler);
        }
    }
}

1

我相信你的集合必须全部都是同一种 MyClass 类型(也就是 T 必须相同),因为编译器不知道你添加了哪些类型到集合中的哪些元素。

换句话说,如果你向列表中添加了两个元素:

list.Add(new MyClass<string>());
list.Add(new MyClass<int>());

然后尝试引用其中一个:

var myItem = list[1];

编译器不知道在元素1处将哪个泛型分配给了MyClass列表,因为元素是在运行时添加的,但泛型是在编译时确定的。

我很确定你想做的事情是无法完成的。


如果您事先知道元素的数量,也许可以使用元组


0

IList

无法定义一个可以接受任何泛型类的任何变体的通用集合...即 IList<MyClass>。泛型类只是为了让开发人员节省编写大量重复代码的捷径,但在编译时,每个泛型类的变体都被转换为具体的类。例如,如果您有MyClass<string>MyClass<int>MyClass<bool>,那么编译器将生成3个不同的类。实现您想要的唯一方法是为您的泛型创建一个接口。

public interface IMyGeneric {
    Type MyType { get; set;}    
}

class MyGeneric<T> : IMyGeneric {

    public MyGeneric() {
        MyType = typeof(T);
    }

    public Type MyType {
        get; set;
    }
}

然后你可以说

IList<IMyGeneric> list = new List<IMyGeneric>();
list.add(new MyGeneric<string>());
list.add(new MyGeneric<int>());

不是在编译时,而是在运行时。 - Dmitry Zvorygin

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