Полиморфные параметры типа в общих коллекциях
Почему компилятор С# не разрешает параметры полиморфного типа (T) в общих коллекциях (т.е. List [T])?
Возьмите класс "A" и "B" , например, где "B" является подклассом "A"
class A { }
class B : A { }
и рассмотрим функцию, которая принимает список типов "A"
void f(List<A> aL) { }
который вызывается со списком типа "B"
List<B> bL = new List<B>();
f(bL);
Приведена следующая ошибка:
ERROR: cannot convert from List<B> to List<A>
Какое семантическое правило нарушается?
Также есть "элегантное" средство для этой цели, кроме цикла и литья каждого элемента (я хочу, пожалуйста, немного сахара)? Спасибо.
Ответы
Ответ 1
List<B>
просто не является подтипом List<A>
. (Я никогда не уверен в том, что "ковариантно" и что "контравариантно" в этом контексте, поэтому я буду придерживаться "подтипа".) Рассмотрим случай, когда вы это делаете:
void Fun(List<A> aa) {
aa(new A());
}
var bb = new List<B>();
Fun(bb); // whoopsie
Если то, что вы хотите сделать, было разрешено, можно было бы добавить A
в список B
, который явно не относится к типу.
Теперь ясно, что можно безопасно читать элементы из списка, поэтому С# позволяет создавать интерфейсы covariant (т.е. только для чтения), которые позволяют компилятору понять, что это невозможно портить через них. Если вам нужен только доступ для чтения, для коллекций обычный IEnumerable<T>
, поэтому в вашем случае вы можете просто сделать метод:
void Fun(IEnumerable<A> aa) { ... }
и используйте методы Enumerable
- большинство должно быть оптимизировано, если базовый тип List
.
К сожалению, из-за того, как работает материал generics С#, классы вообще не могут быть вариантами, а только интерфейсами. И, насколько я знаю, все интерфейсы коллекции "богаче", чем IEnumerable<T>
, являются "read-write". Вы можете технически создать свой собственный ковариантный интерфейс оболочки, который только предоставляет операции чтения, которые вы хотите.
Ответ 2
Возьмите этот маленький пример, почему это не может работать. Представьте, что у нас есть еще один подтип C
of A
:
class A {}
class B : A {}
class C : A {}
Тогда, очевидно, я могу поместить объект C
в список List<A>
. Но теперь представьте себе следующую функцию, берущую A-список:
public void DoSomething (List<A> list)
{
list.Add(new C());
}
Если вы передаете List<A>
, он работает так, как ожидалось, потому что C
является допустимым типом для ввода List<A>
, но если вы передадите List<B>
, то вы не можете поместить C
в этот список.
Для общей проблемы, которая происходит здесь, см. ковариация и контравариантность для массивов.
Ответ 3
Нет ничего неправильного в передаче коллекции B
методу, ожидающему коллекцию A
. Однако есть много вещей, которые могут пойти не так, как в зависимости от того, что вы собираетесь делать с коллекцией.
Рассмотрим:
void f(List<A> aL)
{
aL.(new A()); // oops! what happens here?
}
Очевидно, что здесь существует проблема: если aL
разрешено быть List<B>
, тогда эта реализация приведет к некоторому типу ошибок времени выполнения, либо на месте, либо (что намного хуже), если позже код обрабатывает A
экземпляр, который мы вводим как B
.
Компилятор не позволяет использовать List<B>
как List<B>
, чтобы сохранить безопасность типов и гарантировать, что ваш код не потребует проверки времени выполнения. Обратите внимание, что это поведение отличается от того, что (к сожалению) происходит с массивами - решение дизайнер языка - это компромисс, и они по-разному решали разные случаи:
void f(A[] arr)
{
arr[0] = new A(); // exception thrown at runtime
}
f(new B[1]);
Ответ 4
Я думаю, вы можете искать "общие" модификаторы, которые допускают ковариацию между двумя типичными типами.
http://msdn.microsoft.com/en-us/library/dd469487.aspx
Пример, опубликованный на этой странице:
// Covariant delegate.
public delegate R DCovariant<out R>();
// Methods that match the delegate signature.
public static Control SampleControl()
{ return new Control(); }
public static Button SampleButton()
{ return new Button(); }
public void Test()
{
// Instantiate the delegates with the methods.
DCovariant<Control> dControl = SampleControl;
DCovariant<Button> dButton = SampleButton;
// You can assign dButton to dControl
// because the DCovariant delegate is covariant.
dControl = dButton;
// Invoke the delegate.
dControl();
}
Я не уверен, поддерживает ли С# ковариацию для своих текущих коллекций.
Ответ 5
Вы ошибаетесь, что B наследует от A; но List<B>
не наследуется от List<A>
. List<A> != A
;
Вы можете сделать это:
List<A> aL = new List<A>();
aL.Add(new B());
f (aL)
Вы можете определить тип в void f(List<A> list)
foreach(A a in list)
{
if (a is B)
//Do B stuff
else
//Do A stuff
}
Ответ 6
ваш вопрос очень похож на мой:
ответ заключается в том, что вы не можете сделать это, потому что thoose - это разные типы, созданные классом шаблона, и они не наследуют. что вы можете сделать:
f(bL.Cast<A>());