C# Linq `List<Interface>.AddRange` Метод не работает

У меня есть интерфейс, определенный ниже:

public interface TestInterface{
    int id { get; set; }
}

И два класса Linq-to-SQL, реализующих этот интерфейс:

public class tblTestA : TestInterface{
    public int id { get; set; }
}

public class tblTestB : TestInterface{
    public int id { get; set; }
}

У меня есть IEnumerable списки a и b, заполненные записями базы данных из tblTestA и tblTestB

IEnumerable<tblTestA> a = db.tblTestAs.AsEnumerable();
IEnumerable<tblTestB> b = db.tblTestBs.AsEnumerable();

Однако следующее не допускается:

List<TestInterface> list = new List<TestInterface>();
list.AddRange(a);
list.AddRange(b);

Я должен сделать следующее:

foreach(tblTestA item in a)
    list.Add(item)

foreach(tblTestB item in b)
    list.Add(item)

Есть ли что-то, что я делаю не так? Спасибо за любую помощь

4 ответа

Решение

Это работает в C# 4 из-за общей ковариации. В отличие от предыдущих версий C#, есть преобразование из IEnumerable<tblTestA> в IEnumerable<TestInterface>,

Функциональность была в CLR начиная с v2, но она была представлена ​​только в C# 4 (и типы фреймворков не использовали его до.NET 4 также). Он применяется только к универсальным интерфейсам и делегатам (не классам) и только для ссылочных типов (поэтому нет преобразования из IEnumerable<int> в IEnumerable<object> например.) Это также работает только там, где это имеет смысл - IEnumerable<T> является ковариантным, поскольку объекты только выходят из API, тогда как IList<T> является инвариантным, потому что вы также можете добавлять значения с этим API.

Также поддерживается общая противоположность, работающая в другом направлении - например, вы можете конвертировать из IComparer<object> в IComparer<string>,

Если вы не используете C# 4, тогда Тим предлагает Enumerable.Cast<T> хороший - вы теряете немного эффективности, но это сработает.

Если вы хотите узнать больше об общей дисперсии, у Эрика Липперта есть длинный ряд постов об этом в блоге, и я рассказал об этом на NDC 2010, который вы можете посмотреть на видео-странице NDC.

Вы не делаете ничего плохого List<TestInterface>.AddRange ожидает IEnumerable<TestInterface>, Это не примет IEnumerable<tblTestA> или же IEnumerable<tblTestB>,

Ваш foreach петли работают. В качестве альтернативы, вы можете использовать Cast изменить типы:

List<TestInterface> list = new List<TestInterface>();
list.AddRange(a.Cast<TestInterface>());
list.AddRange(b.Cast<TestInterface>());

AddRange ожидает список объектов интерфейса, а ваши переменные "a" и "b" определены как список объектов производного класса. Очевидно, кажется разумным, что.NET может логически выполнить этот переход и рассматривать их как списки объектов интерфейса, потому что они действительно реализуют интерфейс, эта логика просто не была встроена в.NET до версии 3.5.

Тем не менее, эта способность (называемая "ковариация") была добавлена ​​в.NET 4.0, но пока вы не обновитесь до этого, вы будете зацикливаться на цикле или, возможно, попытаетесь вызвать ToArray() и затем привести результат к TaskInterface[] или, может быть, запрос LINQ для определения каждого элемента и создания нового списка и т. д.

a а также b имеют тип IEnumerable<tblTestA> а также IEnumerable<tblTestB>
В то время как list.AddRange требует, чтобы параметр имел тип IEnumerable<TestInterface>

Другие вопросы по тегам