C # - Есть ли способ разыграть общую коллекцию? - PullRequest
6 голосов
/ 02 февраля 2012

Я был занят дженериками C # 4.0, и теперь я в основном хочу сделать что-то вроде этого:

public abstract class GenericTree<T> : Tree
    where T : Fruit
{
    public Tree(IFruitCollection<T> fruits)
        : base(fruits) { }
}

Базовый класс Tree выглядит следующим образом:

public abstract class Tree
{
    private IFruitCollection<Fruit> fruits;

    public IFruitCollection<Fruit> GetFruits
    {
        get { return fruits; }
    }

    public Tree(IFruitCollection<Fruit> fruits)
    {
        this.fruits = fruits;
    }
}

Вот моя первая проблема. Конструктор GenericTree не может преобразовать универсальную коллекцию в коллекцию фруктов. У меня также есть реализация GenericTree:

public class AppleTree : GenericTree<Apple>
{
    public AppleTree()
        : base(new FruitCollection<Apple>) { }
}

Вот моя вторая проблема. Когда я добавляю фрукты в экземпляр AppleTree, используя myAppleTree.GetFruits.Add (...), я не ограничиваюсь только яблоками. Мне разрешено добавлять все виды фруктов. Я не хочу этого.

Я попытался решить эту проблему, добавив это в GenericTree:

new public IFruitCollection<T> GetFruits
{
    get { return base.GetFruits as IFruitCollection<T>; }
}

Но это тоже невозможно. Это как-то всегда возвращает ноль. Вполне возможно, что это будет решено, когда будет решена моя первая проблема.

Интерфейс IFruitCollection выглядит следующим образом:

public interface IFruitCollection<T> : ICollection<T>
    where T : Fruit { ... }

А класс FruitCollection является простой реализацией класса Collection. О, и, конечно, класс Apple расширяет класс Fruit.

Решение состоит в том, чтобы сделать интерфейс IFruitCollection совместимым как с ковариацией, так и с контравариантностью. Но как мне этого добиться? Ключевые слова параметра «in» или «out» невозможны, поскольку интерфейс ICollection не позволяет этого.

Большое спасибо за вашу помощь!

Ответы [ 2 ]

3 голосов
/ 07 февраля 2012

В ответ на ваш комментарий:

В какой-то момент мне нужен список деревьев. И список Tree<Fruit> не делает этого, потому что не позволяет мне добавлять Tree<Banana> экземпляры. Это, конечно, скажет, что не существует неявной ссылки между Tree<Fruit> и Tree<Banana>.

Основная проблема заключается в том, что вы хотите иметь коллекцию - список деревьев, - которая (косвенно) содержит похожие объекты разных типов. Для устранения неоднозначности этой коллекции из Tree (которая также является коллекцией Fruit), давайте назовем ее Orchard.

Фруктовый сад - (содержит) -> Дерево - (содержит) -> Фрукты

Если вы сделаете неуниверсальный Tree, все элементы Orchard могут относиться к этому типу. Но, как вы заметили, это означает, что вы столкнулись с проблемой того, что деревья небезопасны, и вы можете поместить банан в яблоню. Вам придется решить эту проблему с помощью проверок типов во время выполнения в реализации Tree.

В качестве альтернативы, вы можете создать общий класс Tree<T> where T : Fruit, так что вы имеете безопасность типов по отношению к подклассу Fruit, содержащемуся в дереве. Это означает, что Orchard будет содержать объекты разных типов, что снова требует проверки типов во время выполнения.

(Вы можете создать Orchard со статическим типом безопасности, объявив отдельный метод доступа для каждого типа:

class Tree { }
class Tree<T> : Tree { }
class Trees : IEnumerable<Tree>
{
    Tree<Banana> _bananaTree;
    Tree<Apple> _appleTree;
    //...etc.

    Tree<Banana> GetBananaTree() { return _bananaTree; }
    Tree<Apple> GetBananaTree() { return _appleTree; }
    //...etc.

    public IEnumerator<Tree> GetEnumerator()
    {
        yield return _bananaTree;
        yield return _appleTree;
        //...etc.
    }
}

Но это, вероятно, не то, что вы хотите, поэтому вам нужно иметь приведение где-то .)

Я предполагаю, что вы бы предпочли бросок в Orchard, чем в Tree, и в этом случае я бы предложил что-то подобное для базового подхода:

IDictionary<Type, object> orchard = new Dictionary<Type, object>();

//to retrieve the tree
Tree<Banana> bananaTree = (Tree<Banana>)orchard[typeof(Banana)];

(Конечно, вы можете использовать более конкретный тип, чем object, например Tree, или ICollection, или IEnumerable.)

Я бы пошел дальше и инкапсулировал эту логику в класс Orchard, который мог бы обеспечить менее подробный синтаксис, выполнив приведение в методе доступа:

var bananaTree = orchard.GetTree<Banana>();

где:

public class Orchard
{
    private IDictionary<Type, object> _trees;

    //...

    public Tree<T> GetTree<T>()
    {
        return (Tree<T>)_trees[typeof(T)];
    }
}

В конечном счете, это прекрасный пример того, почему параметры сопутствующего и контравариантного типов в интерфейсах должны быть ограничены позициями вывода и ввода соответственно. Для типов, которые используются как в качестве входных, так и выходных данных, вам понадобятся проверки типов во время выполнения.

1 голос
/ 09 февраля 2012

Я думаю, что нашел свое решение, пока пробовал обходной путь Phoog.Спасибо, phoog!

Сначала я не дал GenericTree его собственную FruitCollection.Большинство, потому что базовый класс часто просматривает свою собственную коллекцию, например, чтобы обновить фрукты.Это привело к тому, что фрукты не обновлялись, потому что они были добавлены в коллекцию GenericTree, а не в базовую коллекцию.

Но в конце концов я понял, что приведение этих коллекций также никогда не будет работать.Я создал еще один класс FruitCollection, который автоматически добавляет и удаляет компоненты в базовую коллекцию FruitCollection, и наоборот.Это решение отлично работает для меня!

public FruitCollection<T, U> : FruitCollection<T>
    where T : U
    where U : Fruit
{
    private FruitCollection<U> baseCollection;

    public FruitCollection(FruitCollection<U> baseCollection)
        : base()
    {
        this.baseCollection = baseCollection;

        // here I added code that throws events whenever the collection is changed
        // I used those events to add/remove fruit to the base collection, and vice versa
        // see the link below.
        ...
    }

    ...
}

public class GenericTree<T>: Tree
    where T : Fruit
{
    private FruitCollection<T> fruits;

    // use the new keyword to hide the base collection
    new public FruiCollection<T> Fruits
    {
        get { return fruits; }
    }

    public GenericTree()
        : base()
    {
        // after hiding the base collection, use base.Fruits to get it
        fruits = new FruitCollection<T, Fruit>(base.Fruits);
    }
}

Это помогло мне: Как обработать событие добавления в список?

...