C# 中的泛型类型:将类型参数限制为集合

Generic type in C#: restricting the type parameter to be a collection

提问人:sbi 提问时间:10/29/2009 更新时间:10/30/2009 访问量:2611

问:

我需要编写一个泛型类,其中类型参数必须是实现的东西。在里面,我需要集合的项目类型(在标记为 的代码片段中)。ICollection<T>MyClass???

class MyClass<TCollection> where TCollection : ICollection<???>
{
  // ...

  public void store(??? obj) { /* put obj into collection */ }

  // ...
}

通常,集合实际上是一本字典。有时,它会是一个简单的列表。

我确切地知道如何在 C++ 中做到这一点。我该怎么做是 C#?

C# 泛型 集合

评论

0赞 SoftMemes 10/29/2009
正如其他人所提到的,您需要一个额外的通用参数。但是,您确定不会仅仅将集合传递给构造函数吗?类真的需要集合的类型吗?
0赞 sbi 10/29/2009
@Freed:你的意思是这样的吗?class MyClass { private ICollection<T> _data; public MyClass(ICollection<T> collection) { _data = collection; } }
0赞 SoftMemes 10/29/2009
@sbi,是的,只要只需要一个未知运行时类型的集合,并且不需要创建集合的新实例或将其公开为 ICollection<T> 以外的公共属性,实际上就不需要对集合类型使用泛型。
0赞 Marc Gravell 10/29/2009
字典和列表是完全不同的;你永远无法互换对待它们
0赞 sbi 10/29/2009
A 与 .为什么该类不允许客户端管理其中任何一个?Dictionary<K,V>List<KeyValuePair<K,V>>

答:

5赞 Jon Skeet 10/29/2009 #1

好吧,你通常会使用另一个类型参数来做到这一点:

class MyClass<TCollection, TElement> where TCollection : ICollection<TElement>
{
  // ...

  public void store(TElement obj) { }

  // ...
}

在这种情况下,您真的需要吗?你能凑合着过吗?TCollectionTElement

评论

0赞 sbi 10/29/2009
是的,用户需要能够指定使用的集合。- 给定您的类定义,用户是否必须同时指定两者,或者可以从传递的集合中自动推导出来?(我之所以问这个问题,是因为这些集合通常是嵌套的字典,这很容易打字。MyClassTCollectionTElementTElement
0赞 Jon Skeet 10/29/2009
您必须显式指定这两个类型参数。不过,在某些情况下,您可以编写一个帮助程序方法来利用泛型类型推断。这取决于确切的用例。
0赞 sbi 10/29/2009
鉴于该类被用作其他类的成员,我想没有办法使用泛型类型推断?
0赞 Jon Skeet 10/29/2009
如果您有相关类型的实例,则可以通过泛型方法使用泛型类型推断(然后可以创建泛型类型的实例)。
0赞 sbi 10/29/2009
@Jon:每个实例只需要一个实例,但它们有许多不同的实例,并且数据结构仍然会更改。MyClass:(
1赞 djdd87 10/29/2009 #2

你就不能这样做吗?

class MyClass<T, TCollection>  where T: YourTypeOrInterface
                               where TCollection : ICollection<T>
{
    public void store(T obj) { }
}

而且,没有经过测试,但我能想到的唯一其他方法是在存储项目时指定类型:

class MyClass<TCollection> where TCollection : System.Collections.ICollection
{

    TCollection Collection;

    public void Store<T>(T obj) 
    {
        ((ICollection<T>)this.Collection).Add(obj);
    }

}
3赞 Christian Hayter 10/29/2009 #3

最简单的做法是只指定元素类型,并在需要的地方进行硬编码,例如ICollection<T>

class MyClass<T> {

    private ICollection<T> _items;

    public MyClass(ICollection<T> items) {
        _items = items;
    }

    public void Store(T obj) {
        _items.Add(obj);
    }

    public ICollection<T> Items {
        get {
            return _items;
        }
    }
}

我建议您将集合实例传递给构造函数,而不是在内部创建一个。它使类更简单、更“通用”(请原谅双关语),并允许您使用非默认构造函数构造集合实例,例如,使用非默认比较器的字典。

重新编辑(第 3 次尝试):使用类继承和命名空间别名进行模拟在某种程度上都是可以的,但在某些情况下,这两种抽象都会崩溃。这段代码是我发现的最简单的实际编译代码。typedef

步骤 1 - 定义这些类:

// This KeyValuePair was being used to simulate a tuple. We don't need to simulate a tuple when we have a concrete class.
class BazAndListOfWrgl {
    Baz Baz { get; set; }
    List<Wrgl> Wrgls { get; set; }
}

// Simple typedef.
class BazAndListOfWrglDictionary : Dictionary<Bar, BazAndListOfWrgl> { }

步骤 2 - 定义这些命名空间别名。所有标识符都必须是完全限定的。引用的所有类型都必须已在不同的物理文件中定义(因为命名空间别名必须位于文件中的所有代码之前)。

using OuterDictionary = System.Collections.Generic.Dictionary<MyNamespace.Foo, MyNamespace.BazAndListOfWrglDictionary>;
using OuterDictionaryItem = System.Collections.Generic.KeyValuePair<MyNamespace.Foo, MyNamespace.BazAndListOfWrglDictionary>;

第 3 步 - 像这样使用它们:

class Program {

    static void Main() {

        // List-based example.
        var listWrapper = new MyClass<BazAndListOfWrgl>(new List<BazAndListOfWrgl>());
        listWrapper.Store(new BazAndListOfWrgl());
        Console.WriteLine(listWrapper.Items.Count);

        // Dictionary-based example.
        var dictionaryWrapper = new MyClass<OuterDictionaryItem>(new OuterDictionary());
        dictionaryWrapper.Store(new OuterDictionaryItem(new Foo(), new BazAndListOfWrglDictionary()));
        Console.WriteLine(dictionaryWrapper.Items.Count);

    }
}

理由是:不能是命名空间别名,因为命名空间别名不能相互依赖。 并且不能是派生类,否则编译器不会将一个类识别为另一个类的元素。BazAndListOfWrglDictionaryOuterDictionaryOuterDictionaryItem

评论

0赞 sbi 10/29/2009
此类需要存储不同类型的集合。我想尽可能简单地做到这一点 - 我只是不知道如何在 C# 中做到这一点。:-x
0赞 Christian Hayter 10/29/2009
该字段将存储所有类型的集合。我认为您将泛型与普通接口混淆了。ICollection<T> _items
0赞 sbi 10/29/2009
@Christian:这是弗里德在他的评论中已经建议的。是的,这会起作用,但它也需要拼写两次 - 如果实际上是三个嵌套词典,那就不好了。我想我被 C++ 强大的模板宠坏了......TICollection<T>
0赞 Christian Hayter 10/29/2009
对不起,我不明白你。为什么你必须拼出两次 T?如果是三个嵌套词典,会有什么区别?通过将三个嵌套词典视为一个集合接口,您可以隐藏所有复杂的功能,并假装它是一个简单的列表。如果要访问类中的字典,请不要将其设为泛型,因为您所做的任何操作都对非字典集合无效。ICollection<T>KeyValuePair
0赞 Christian Hayter 10/29/2009
你给我们一个实际的例子,(a)一个处理列表的非泛型类,以及(b)一个处理字典的非泛型类。然后,我们可以建议如何将这两个类重构为一个泛型类。
2赞 Rob van Groenewoud 10/29/2009 #4

这对我有用:

class MyClass<TCollection,T> where TCollection : ICollection<T> , new()
{
    private TCollection collection;           

    public MyClass()
    {
        collection = new TCollection();
    }

    public void Store(T obj)
    {
        collection.Add(obj);
    }

    public TCollection Items
    {
        get { return collection; }
    }
}

用法:

MyClass<List<string>, string> myclass = new MyClass<List<string>, string>();
myclass.Store("First element");
myclass.Store("Second element");
myclass.Items.ForEach(s => Console.WriteLine(s));

编辑: 当 T 变得越来越复杂时,你可能想看看 using 指令,你可以(错误地)将其用作 typedef。

using HugeType = System.Collections.Generic.Dictionary<int, string>; // Example 'typedef'
...

//Create class, note the 'typedef' HugeType
MyClass<List<HugeType>, HugeType> myclass = new MyClass<List<HugeType>, HugeType>();
//Fill it
HugeType hugeType1 = new HugeType();
hugeType1.Add(1, "First");
hugeType1.Add(2, "Second");
HugeType hugeType2 = new HugeType();
hugeType1.Add(3, "Third");
hugeType1.Add(4, "Fourth");
myclass.Store(hugeType1);
myclass.Store(hugeType2);
//Show it's values.
myclass.Items.ForEach(element => element.Values.ToList().ForEach(val => Console.WriteLine(val)));

评论

1赞 sbi 10/29/2009
这就是我目前正在做的事情,除了你有的地方,我有类似的东西.(我有没有说过我失踪得很厉害?不?好吧,所以:我失踪得很厉害。非常糟糕。事实上,这是 C# 的主要烦恼之一,它错过了。MyClass<List<string, string>MyClass<Dictionary< foo, Dictionary<bar, KeyValuePair<baz, List<wrgl>>>>, <bar, KeyValuePair<baz, List<wrgl>>>>typedeftypedeftypedef
0赞 Rob van Groenewoud 10/29/2009
我理解您的担忧,编辑了我的答案,不确定是否推荐此解决方案,但它似乎适用于我的例子。
0赞 sbi 10/30/2009
@Rob:是的,我刚才找到了指令。但是,IIRC,它只能出现在命名空间范围内,而不能出现在类中。这很烦人。using
1赞 Rob van Groenewoud 10/30/2009
@sbi:你对范围的看法是对的。除此之外,VS 自动补全还会再次插入完整的字符串而不是“别名”,这也非常烦人。
1赞 Paul Turner 10/30/2009 #5

是否有任何公开类型的公共成员?TCollection

如果有任何这样的成员,你是否获得了公开可以公开的直接类型的任何价值?TCollectionICollection<T>

您的客户端应该实现该接口,因此它不会真正影响您的对象;您只会调用该接口,而不是其特定类型。因此,对象只需要知道集合中存储的元素类型。ICollection<T>

在这个前提下,类可以这样写:

public class MyClass<T>
{
    public MyClass(ICollection<T> collection) 
    { 
        /* store reference to collection */ 
    }

    // ...

    public void store(T obj) 
    { 
        /* put obj into collection */ 
    }

    // ...
}

评论

0赞 sbi 10/31/2009
“有没有公开的公众成员暴露了这种类型?”是的,有。我可以侥幸逃脱,但它会导致丑陋且可能容易出错的代码。TCollectionICollection<T>