Вопрос

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

class A {}
class B : A {}

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

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

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

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

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

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

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

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

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

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

Это было полезно?

Решение

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

http://blogs.msdn.com/ ericlippert / архив / теги / ковариации + и + контрвариация / default.aspx

Другие советы

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

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

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

class MyList<T> {
    void AddRange<U>(IEnumerable<U> items) where U: T {
        foreach (U item in items) {
            Add(item);
        }
    }
}

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

listOfAItems.AddRange(listOfBItems.Cast<A>());

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

listOfAItems.AddRange(listOfBItems.Cast<A>());

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

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);

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

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 < A > .AddRange < B > (MyList < B >). Это терпит неудачу, если аргумент не является перечисляемым или если наследование типов не работает, поэтому он удовлетворяет максимальному требованию безопасности типов моего вопроса.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top