Хотя мы можем наследовать от базового класса / интерфейса, почему мы не можем объявить List<>
использование одного и того же класса / интерфейса?
interface A
{ }
class B : A
{ }
class C : B
{ }
class Test
{
static void Main(string[] args)
{
A a = new C(); // OK
List<A> listOfA = new List<C>(); // compiler Error
}
}
Есть ли способ обойти?
Ответы:
Способ сделать эту работу состоит в том, чтобы перебрать список и привести элементы. Это можно сделать с помощью ConvertAll:
Вы также можете использовать Linq:
источник
ConvertAll
илиCast
?Прежде всего, прекратите использовать непонятные имена классов, такие как A, B, C. Используйте Animal, Mammal, Giraffe или Food, Fruit, Orange или что-то, где отношения ясны.
Тогда ваш вопрос таков: «Почему я не могу назначить список жирафов переменной типа животного, поскольку я могу назначить жирафа переменной животного типа?»
Ответ: предположим, вы могли бы. Что может пойти не так?
Ну, вы можете добавить тигра в список животных. Предположим, мы разрешаем вам поместить список жирафов в переменную, которая содержит список животных. Затем вы пытаетесь добавить тигра в этот список. Что случается? Вы хотите, чтобы список жирафов содержал тигра? Вы хотите аварии? или вы хотите, чтобы компилятор защитил вас от сбоя, сделав присвоение незаконным в первую очередь?
Мы выбираем последнее.
Этот вид преобразования называется «ковариантным» преобразованием. В C # 4 мы позволим вам делать ковариантные преобразования на интерфейсах и делегатах, когда известно, что преобразование всегда безопасно . Смотрите мои статьи в блоге о ковариации и контравариантности для деталей. (По этой теме будет новая статья в понедельник и четверг этой недели.)
источник
IEnumerable
вместоList
? т.е.List<Animal> listAnimals = listGiraffes as List<Animal>;
не возможно, ноIEnumerable<Animal> eAnimals = listGiraffes as IEnumerable<Animal>
работает.IEnumerable<T>
иIEnumerator<T>
оба помечены как безопасные для ковариации, и компилятор подтвердил это.Процитирую великое объяснение Эрика
Но что, если вы хотите выбрать для сбоя во время выполнения вместо ошибки компиляции? Обычно вы используете Cast <> или ConvertAll <>, но тогда у вас будут 2 проблемы: он создаст копию списка. Если вы добавите или удалите что-то в новом списке, это не будет отражено в исходном списке. А во-вторых, это большая производительность и потеря памяти, поскольку он создает новый список с существующими объектами.
У меня была та же проблема, и поэтому я создал класс-обертку, который может создавать общий список, не создавая совершенно новый список.
В исходном вопросе вы можете использовать:
а вот класс-обертка (+ метод расширения CastList для простоты использования)
источник
Если вы используете
IEnumerable
вместо этого, он будет работать (по крайней мере, в C # 4.0, я не пробовал предыдущие версии). Это просто актерский состав, конечно, это все равно будет список.Вместо того -
List<A> listOfA = new List<C>(); // compiler Error
В оригинальном коде вопроса используйте -
IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
источник
List<A> listOfA = new List<C>(); // compiler Error
исходного кода вопроса введитеIEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
Что касается того, почему это не работает, может быть полезно понять ковариацию и противоположность .
Просто чтобы показать, почему это не должно работать, приведем изменение в коде, который вы предоставили:
Должно ли это работать? Первый элемент в списке имеет тип "B", но тип элемента DerivedList - C.
Теперь предположим, что мы действительно хотим создать обобщенную функцию, которая работает со списком некоторого типа, который реализует A, но нам все равно, что это за тип:
источник
Вы можете привести только к спискам только для чтения. Например:
И вы не можете сделать это для списков, которые поддерживают сохранение элементов. Причина почему:
Что теперь? Помните, что listObject и listString на самом деле являются одним и тем же списком, поэтому listString теперь имеет элемент object - это не должно быть возможно, и это не так.
источник
Мне лично нравится создавать библиотеки с расширениями для классов
источник
Потому что C # не позволяет этот тип
наследованиеконверсия на данный момент .источник
Это продолжение блестящего ответа BigJim .
В моем случае у меня был
NodeBase
класс соChildren
словарем, и мне нужен был способ сделать O (1) общий поиск от детей. Я пытался вернуть поле частного словаря в получателеChildren
, поэтому, очевидно, я хотел избежать дорогостоящего копирования / итерации. Поэтому я использовал код Bigjim, чтобы кастовалиDictionary<whatever specific type>
к родовомуDictionary<NodeBase>
:Это сработало хорошо. Однако в итоге я столкнулся с несвязанными ограничениями и в итоге создал абстрактный
FindChild()
метод в базовом классе, который вместо этого будет выполнять поиск. Как оказалось, это устранило необходимость в приведенном словаре в первую очередь. (Я смог заменить его на простойIEnumerable
для моих целей.)Таким образом, вопрос, который вы можете задать (особенно если производительность - это проблема, запрещающая вам использовать
.Cast<>
или.ConvertAll<>
):«Мне действительно нужно разыграть всю коллекцию, или я могу использовать абстрактный метод для хранения специальных знаний, необходимых для выполнения задачи, и, таким образом, избежать прямого доступа к коллекции?»
Иногда самое простое решение - лучшее.
источник
Вы также можете использовать
System.Runtime.CompilerServices.Unsafe
пакет NuGet для создания ссылки на то же самоеList
:Учитывая приведенный выше пример, вы можете получить доступ к существующим
Hammer
экземплярам в списке, используяtools
переменную. ДобавлениеTool
экземпляров в список вызываетArrayTypeMismatchException
исключение, посколькуtools
ссылается на ту же переменную, что иhammers
.источник
Я прочитал всю эту ветку, и я просто хочу указать на то, что мне кажется несоответствием.
Компилятор не позволяет вам выполнять присваивание с помощью списков:
Но компилятор прекрасно работает с массивами:
Спор о том, известно ли, что присвоение является безопасным, разбивается здесь. Назначение, которое я сделал с массивом, небезопасно . Чтобы доказать это, если я продолжу это:
Я получаю исключение времени выполнения "ArrayTypeMismatchException". Как это объяснить? Если компилятор действительно хочет помешать мне сделать что-то глупое, он должен был помешать мне сделать присвоение массива.
источник