Общий тип в C #: ограничение параметра типа коллекцией - PullRequest
3 голосов
/ 29 октября 2009

Мне нужно написать универсальный класс, в котором параметр типа должен быть чем-то, реализующим ICollection<T>. Внутри MyClass мне нужен тип элемента коллекции (во фрагменте кода, отмеченном как ???).

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

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

  // ...
}

Часто коллекция фактически представляет собой словарь. Иногда это будет что-то простое, как список.

Я точно знаю, как это сделать в C ++. Как бы я сделал это C #?

Ответы [ 5 ]

5 голосов
/ 29 октября 2009

Ну, вы обычно делаете это, используя другой параметр типа:

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

  public void store(TElement obj) { }

  // ...
}

Вам действительно нужно TCollection в этом случае? Не могли бы вы просто обойтись с TElement?

3 голосов
/ 29 октября 2009

Самое простое, что нужно сделать 1002 *, это просто указать только тип элемента и жесткий код 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;
        }
    }
}

Я рекомендую передать экземпляр коллекции конструктору, а не создавать его внутри. Это делает класс проще и более «универсальным» (извините за каламбур) и позволяет создавать экземпляры коллекций с помощью конструкторов, отличных от заданных по умолчанию, например, словарь с компаратором не по умолчанию.

RE-EDIT (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);

    }
}

Причина: BazAndListOfWrglDictionary не может быть псевдонимом пространства имен, поскольку псевдонимы пространства имен не могут зависеть друг от друга. OuterDictionary и OuterDictionaryItem не могут быть производными классами, поскольку в противном случае компилятор не распознает один из них как элемент другого.

2 голосов
/ 29 октября 2009

Это работает для меня:

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

EDIT: Когда T усложняется, вы можете взглянуть на , используя директиву , вы можете (неправильно) использовать его как 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 голос
/ 30 октября 2009

Есть ли открытые члены, которые выставляют тип TCollection?

Если есть такие члены, получаете ли вы какое-либо значение, выставляя прямой тип TCollection, где вы могли бы выставить ICollection<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 */ 
    }

    // ...
}
1 голос
/ 29 октября 2009

Разве вы не можете просто сделать это?:

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

}
...