Проблема в том, что генерики не следуют тем же правилам присваивания, что и обычные классы. Чтобы универсальный класс мог быть назначен, типы должны точно соответствовать. Вот пример, чтобы показать вам, почему:
Предположим, у нас есть:
class A { }
class B: A { }
полиморфизм говорит, что мы можем назначить экземпляры B переменной типа A. Это потому, что B является A. Теперь рассмотрим список:
var a_s = new List<A>();
a_s.Add(new A());
var b_s = (List<B>)a_s;
var b = b_s[0]; // But b_s[0] is the same as a_s[0], which is an A instance, and A's can't be assigned to B's!! What happened?!
Вот почему универсальные классы должны точно совпадать. Подходят более специализированные входы, так как они могут автоматически up приводиться к соответствующему типу. Однако, более специализированных выходов нет - вы не можете вниз привести к небрежности, что и происходит в (гипотетическом) коде выше. Это очевидно в языках с разным синтаксисом upcast и downcast, таких как static_cast
и dynamic_cast
в C ++ и :>
и :?>
.
в F ++.
Это различие между типами ввода и вывода действительно важно и формализовано в C # как in
и out
универсальные параметры, соответствующие понятиям ковариации и контравариантности. Это относится только к интерфейсам.
Учитывая, что полиморфное поведение обобщенных аргументов логически невозможно, вы, вероятно, делаете небольшую ошибку в своем проекте и столкнетесь с ней, даже если вы реорганизовали использование интерфейсов. Тщательно проанализируйте свой дизайн и отодвиньте полиморфную логику от обобщений. Вы можете использовать рефлексию, чтобы обойти это ограничение, но на самом деле вы не должны этого делать, если у вас нет очень веских причин.