C# - вернуть типизированную коллекцию
Есть система классов
class Base
class Derived1 : Base
class Derived2 : Base
Есть коллеции объектов наследованных классов
List<Derived1> Derived1Collection List<Derived2> Derived2Collection
Хочу дженерик метод для класса, где хранятся эти коллекции, который вернёт любую из этих коллекций. Типа такого
GetData<Derived1>();
Сделал так
public static IEnumerable<T> GetData<T>() where T : Base => typeof(T) switch { _ when typeof(T) == typeof(Derived1) => Derived1Collection.Cast<T>(), _ when typeof(T) == typeof(Derived2) => Derived2Collection.Cast<T>(), _ => throw new Exception($"{typeof(T)} is not supported."), };
Ошибок компиляции не показывает, но ещё не юзал.
Мне не нравится - некрасиво. Лишняя хреть typeof(T) перед свичом. И зачем-то требует Cast, а без него пишет
Cannot implicitly convert type List<Derived1> to IEnumerable<T>
Что за глупость? Не может сконверить более унаследованный класс в менее унаследованный? Причём менее унаследованный что для коллекции, что для типа коллекции - Derived1 кастуется в Base, List кастуется в IEnumerable - у нас же ковариация тут работает для IEnumerable .
Ошибок компиляции не показывает, но ещё не юзал.
Отвлекаешь нас непроверенным нерабочим кодом? Как протестишь - приходи снова!
с одной стороны, молодец, решил
С другой - опять же нифига не понял, в чем проблема была
С другой - опять же нифига не понял, в чем проблема была
Это заморочки ООП и генериков с коллекциями. В классическом ООП есть только объекты, который имеют класс, а классы существуют в иерерхии. Далле определяется кому и что можно присваивать и т.д. А теперь мы вводим две категории: 1) обычные объекты плюс 2) коллекции (ну или любой другой класс) с параметром обычных объектов. Каждый существует в своей собственной иерархии: иерархия обычных объектов и иерархия коллекций (но с параметром класса объекта). Теперь надо вводить более сложные правила в плане кому и что можно присваивать и кто с кем может работать. Называется ковариантность (движение в одном направлении в обоих иерархиях) и контравариантность (движение в разных направлениях).
На самом деле там корни в старой
махровой функциональщине, а потом уже через долгое время это скоммуниздили и переиначили оопешники. По любому гемор обеспечен.
Хранить коллекции в общей куче и выдавать вот так?
return commonCollection.OfType();
Не пойдёт - это при каждом обращении к коллекции придётся проходить по всем элементам и фильтровать. А если кешировать, то вот у меня уже есть коллекции, разделённые по типу.
Мой вариант нормальный и уже работает. Я просто хочу узнать, можно ли лучше организовать, и почему каст требуется. Узнал уже, что каст нужен из-за ограничений языка - создатели так решили, что производный тип к базовому привести можно, а коллекцию производных типов к коллекции базовых - нельзя. Они считают это более типобезопасным. Ещё. Неявным образом нельзя, но если ты хочешь явно, то кастуй.
public static IEnumerable<T> GetData<T>() where T : Base => typeof(T) switch { _ when typeof(T) == typeof(Derived1) => Derived1Collection.Cast<T>(), _ when typeof(T) == typeof(Derived2) => Derived2Collection.Cast<T>(), _ => throw new Exception($"{typeof(T)} is not supported."), };
Такой вариант тоже работает - приведение типа сразу для всей коллекции вместо кастинга каждого её элемента:
_ when typeof(T) == typeof(Derived1) => (IEnumerable<T>)Derived1Collection,
Это даже получше будет, т.к. не надо при каждом обращении к GetData проходить по всей коллекции.
typeof(T) перед выражением switch, кстати, не используется. Просто это требования синтаксиса свича. Лучше, наверное, пачку if-else вместо этого корявого свича.
Да, пачка ифов лучше. Ну, вроде теперь почти идеально.
public static IEnumerable<T> GetData<T>() where T : Base { if (typeof(T) == typeof(Derived1)) return (IEnumerable<T>)Derived1Collection; else if (typeof(T) == typeof(Derived2)) return (IEnumerable<T>)Derived2Collection; else throw new Exception($"{typeof(T)} is not supported."); }
Узнал уже, что каст нужен из-за ограничений языка - создатели так решили, что производный тип к базовому привести можно, а коллекцию производных типов к коллекции базовых - нельзя
не совсем
вот такое ведь работает
IEnumerable<Base> result = new List<Derived1>();
Вот почему ты такой? Когда ты спрашиваешь - то тебе ребята на стековерфлоу всё разжевали, помогли решить твою задачку.
А как тебя просят - нарисуй цикл, помоги начинающим товарищам запустить квантовый блокчейн, так сразу в кусты убежал.
Для тебя делов то 3-4 минуты времени, сам при этом новые вещи для себя откроешь, если вдруг от индусов уйдёшь, помогай!
Вот почему ты такой? Когда ты спрашиваешь - то тебе ребята на стековерфлоу всё разжевали, помогли решить твою задачку.
А как тебя просят - нарисуй цикл, помоги начинающим товарищам запустить квантовый блокчейн, так сразу в кусты убежал.
Для тебя делов то 3-4 минуты времени, сам при этом новые вещи для себя откроешь, если вдруг от индусов уйдёшь, помогай!
Тоже спросите ребят на Stackoverflow.
public static IEnumerable<T> GetData<T>() where T : Base => typeof(T) switch { _ when typeof(T) == typeof(Derived1) => Derived1Collection, //здесь компилятор не уверен, что твой Т является Derived1 ... };
Да, такое объяснение тоже встречал - в compile time он не уверен, и ему нужно явное приведение. А в run time все дженерики становятся явными типами, поэтому по идее код должен нормально выполняться, если заигнорить эту ошибку компиляции. Но меня вариант с явным приведением устраивает - не будем ломать статический анализ компилятора.