Добавление коллекции подкласса с AddRange - PullRequest
1 голос
/ 04 мая 2009

Если у меня есть эти два класса:

class A {}
class B : A {}

и я создаю список , но я хочу добавить в него список , вызвав List .AddRange (List ), но компилятор отказывается:

Argument '1': cannot convert from 'System.Collections.Generic.List<A>'
to 'System.Collections.Generic.IEnumerable<B>

, который я полностью понимаю, потому что IEnumerable не наследуется от IEnumerable , его универсальный тип имеет наследование.

Мое решение состоит в том, чтобы перечислять через Список и индивидуально добавлять элементы, потому что Список .Add (элемент A) будет работать с элементами B:

foreach(B item in listOfBItems)
{
    listOfAItems.Add(item);
}

Однако, это довольно не выразительно, потому что я хочу просто AddRange.

Я мог бы использовать

List<B>.ConvertAll<A>(delegate(B item) {return (A)item;});

но это излишне запутано и неправильно, потому что я не конвертирую, я кастую.

Вопрос : Если бы я написал свою собственную коллекцию, подобную списку, какой метод я бы добавил к ней, что позволило бы мне копировать коллекцию B в коллекцию A в виде однострочного аналога Список .AddRange (Список ) и сохраняют максимальную безопасность типов. (Под максимумом я подразумеваю, что аргумент является одновременно проверкой коллекции и наследования типов.)

Ответы [ 6 ]

8 голосов
/ 04 мая 2009

Действительно, универсальные типы не являются вариантами прямо сейчас. В C # 4.0 IEnumerable <B> будет конвертироваться в IEnumerable <A> , если B конвертируется в A посредством эталонного преобразования . Подробнее о дизайне этой функции см .:

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

1 голос
/ 03 марта 2016

Если вы оказались в ситуации, когда универсальные типы не являются вариантами, следующий метод расширения может облегчить вашу жизнь:

public static void AddRange<TList,TOther>(this List<TList> list, IEnumerable<TOther> collection) where TOther: TList {
    foreach(TOther e in collection) {
        list.Add(e);
    }
}

Вместо того, чтобы наследовать от List<T> или иметь этот метод в каком-либо служебном классе, его использование в качестве метода расширения упрощает использование. Вы также можете воспользоваться выводом, так что этот ранее недействительный вызов станет действительным без каких-либо изменений:

List<Animal> animals;
List<Dog> dogs;
animals.AddRange(dogs);
1 голос
/ 17 июня 2013

Мне удалось добиться этого с помощью LINQ ...

listOfAItems.AddRange(listOfBItems.Cast<A>());
1 голос
/ 04 мая 2009

Разве вы не можете просто сделать:

listOfAItems.AddRange(listOfBItems.Cast<A>());
1 голос
/ 04 мая 2009

К сожалению, это не работает, потому что дженерики в .net (пока) не поддерживают ковариацию.

Вы можете создать небольшой вспомогательный метод или класс для решения этой проблемы.

Если вы реализуете свой собственный класс списка, вы можете добавить ковариацию, используя дополнительный универсальный параметр:

class MyList<T> {
    void AddRange<U>(IEnumerable<U> items) where U: T {
        foreach (U item in items) {
            Add(item);
        }
    }
}
0 голосов
/ 04 мая 2009

Единственное, что я могу придумать, это

public class MyList<T> : List<T>
{
    public void AddRange<Tother>(IEnumerable<Tother> col)
        where Tother: T
    {    
        foreach (Tother item in col)
        {
            this.Add(item);
        }
    }
}

Вызов этого означает создание MyList .AddRange (MyList ). Это терпит неудачу, если аргумент не является перечисляемым или если наследование типов не работает, поэтому он удовлетворяет максимальному требованию безопасности типов моего вопроса.

...