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>