С# Общий список общего списка нескольких типов

Вот абстракция и упрощение моей проблемы:

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

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...]
}

Я хочу иметь возможность создавать Игрушки со многими различными типами, а затем вставлять их в Коробку с другим указанным типом. Затем я хотел бы иметь возможность добавлять ящики вместе, возвращая Box с самым большим типом.

Я действительно не знаю, с чего начать. Список родового класса с несколькими типами действительно бросает меня на цикл. Я читал различные статьи об использовании абстрактного класса или интерфейса, но не нашел примера или чего-либо, что бы делало что-то похожее на то, что я пытаюсь сделать.

Любая помощь, которую любой может предоставить, будет очень оценена.

Решение может быть в С# 4.0.

Возможное уточнение будущего:

Я хочу, чтобы Toy была общей и принимала аргумент при создании экземпляра, потому что Toy также должен иметь список как член.

Вложенный список внутри игрушки - моя главная проблема. Затем я хочу, чтобы список внутри Box содержал Toys, но каждая игрушка имеет конструктор разного типа.

Обновление:

Я установил коробку в коробку, которая была опечаткой.

Обновление 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, чтобы он был общим. Затем я использовал отражение для создания динамически типизированной игрушки. Спасибо всем.

Ответ 1

Код, который вы строите, будет лучше всего понят, если он хорошо моделирует реальность.

Способом моделировать "A of B" является использование дженериков. Набор типов ящиков, которые могут содержать один вид, будет моделироваться как Box<T>. Коробка, в которой можно хранить только игрушки, будет Box<Toy>. Множество видов ящиков, которые могут содержать одну вещь, и эта вещь должна быть игрушкой, была бы Box<T> where T : Toy.

Пока все хорошо. Но понятие Toy<T> не сопоставляется ни с чем в реальной жизни. У вас может быть коробка с печеньем или коробкой с игрушками, но у вас нет игрушек с печеньем, игрушкой кукол или игрушкой жирафов. Понятие "игрушка" не имеет никакого смысла, поэтому не моделируйте его.

Более разумной вещью для модели было бы "есть общий класс вещей, называемых игрушками. Нет ни одной вещи, которая просто игрушка, каждая игрушка - более конкретная игрушка. Мяч - игрушка. кукла - это игрушка". Итак, модель, которая:

abstract class Toy {}
class Doll : Toy {}
class Ball : Toy {}

Вы сказали

Я хочу, чтобы Toy была общей и принимала аргумент при создании экземпляра, потому что Toy также должен иметь список как член.

Почему? У игрушки нет списка вещей. Так что не моделируйте это. Скорее, поле логически смоделировано как список игрушек, которые находятся внутри коробки. (Или, поскольку в коробке обычно не применяется заказ, а в коробке есть только уникальные игрушки, возможно, набор игрушек будет лучше.)

Я хочу иметь возможность создавать Игрушки со многими различными типами, а затем вставлять их в Коробку с другим указанным типом.

OK. Таким образом, операция на 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(); }

Все очень скучно. Теперь мы подошли к интересному. Это работает только на С# 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);

Результатом слияния коробки с куклами с коробкой шаров является коробка игрушек.

Этот последний бит работает только потому, что IEnumerable<T> является ковариантным в С# 4. В С# 3 было бы сложнее получить право; вам нужно сделать что-то вроде:

Box<Toy> toybox2 = Box<Toy>.MergeBoxes(ballbox.Cast<Toy>(), dollbox.Cast<Toy>());

Это имеет смысл?

Ответ 2

Я не уверен, что это то, что вы на самом деле хотите сделать, так как это немного хаки. Если вам нужны списки всех возможных типов игрушек, в том числе те, которые могут не знать о Box, вам нужно будет использовать свои общие параметры для фильтрации правильных игрушек. Я не рекомендую это делать, если Box не является просто чистым списком для заполнения обычных вещей (например, BlackBoard).

Например, у вас может быть только один список <Object> и все методы проверяют правильность типа содержимого перед их манипулированием. Вы также можете использовать словарь < Тип, Object > чтобы пропустить поэтапный шаг фильтрации, но вызывают некоторые проблемы с наследованием (список животных не будет включать вещи, добавленные в список жирафов).

Вот простая реализация подхода словаря:

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
{
    ...
}

Ответ 3

Я цитирую MSDN:

Общие классы инкапсулируют операции которые не являются специфическими для конкретного тип данных. Наиболее частое использование для родовые классы с коллекциями как связанные списки, хеш-таблицы, стеки, очереди, деревья и т.д., где такие операции, как добавление и удаление предметы из коллекции выполняются почти так же независимо от типа данных, являющихся сохранены.

Учитывая ваше описание, ближайший объект, к которому это применимо, будет Box, но с другой стороны, вы знаете, что он собирается содержать игрушки, и имеет максимальный ToySize, и что у Игрушки есть тип (Ball, Horse...). Возможно, я ошибаюсь, но использование дженериков может и не быть необходимым. Я думаю что-то вроде:

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

Если у вас нет конкретной причины использования дженериков.

Ответ 4

У меня возникла эта проблема при построении списка общих типов, которые я не знал об общей части во время компиляции, они должны были быть динамическими как-то.

Я решил свою проблему, имея общий класс следующим образом:

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

ПРЕДУПРЕЖДЕНИЕ: Вы можете получить доступ к членам детей, но они не совпадают со всеми в списке! Это позволит вам скомпилировать его, но он может сломаться во время выполнения!

Например, у меня есть метод DoSomething() в моем классе "EvenOtherType". Если я попытаюсь вызвать это, перейдя по списку, очевидно, что строка и int-версии будут сбой во время выполнения!

Будьте осторожны!

Ответ 5

В вашей последней строке "каждая игрушка имеет другой конструктор" подразумевает, что вам нужен либо базовый класс "Игрушка", с каждой конкретной игрушкой, наследующей от этого класса:

public abstract class Toy{} // abstract base class

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

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

Причина, в которой вы нуждаетесь, заключается в том, что если вы используете один класс для всех типов Toy, вы не сможете изменить конструктор на основе типа..., который просто не поддерживает поведение генериков.