Метод 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)
Есть ли что-то, что я делаю неправильно?Спасибо за любую помощь
Решение
Это работает в C# 4 из-за общая ковариация.В отличие от предыдущих версий C#, здесь имеется преобразование из IEnumerable<tblTestA>
к IEnumerable<TestInterface>
.
Эта функциональность присутствовала в CLR начиная с версии 2, но была представлена только в 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, который вы можете посмотреть на Страница видео НДЦ.
Другие советы
Вы не делаете ничего плохого: 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, но пока вы не будете обновлять до этого, вы будете застрять с циклом, или, возможно, попробуйте позвонить наTaskInterFace [], или, возможно, запрос LINQ для случая каждого элемента и создать новый список и т. Д.
a
и b
имеют тип IEnumerable<tblTestA>
и IEnumerable<tblTestB>
Хотя list.AddRange
требует параметра, чтобы иметь тип типа IEnumerable<TestInterface>