Полиморфные параметры типа в общих коллекциях

Почему компилятор С# не разрешает параметры полиморфного типа (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>());