список C# Linq.Метод 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> to IEnumerable<object> например.) Работает только там, где это имеет смысл - IEnumerable<T> является ковариантным, поскольку объекты только "выходят" из API, тогда как IList<T> is инвариант потому что вы также можете добавлять значения с этим API.

Generic contravariance также поддерживается, работает в другом направлении-так, например, вы можете конвертировать из IComparer<object> to 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 " varaibles определены как список объектов производного класса. Очевидно, кажется разумным, что .NET может логически сделать этот прыжок и рассматривать их как списки объектов интерфейса, потому что они действительно реализуют интерфейс, эта логика просто не была встроена в .NET через 3.5.

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


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