c # Linq `List <Interface>.AddRange` Метод не работает - PullRequest
6 голосов
/ 14 июля 2010

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

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)

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

Ответы [ 4 ]

8 голосов
/ 14 июля 2010

Это работает в 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, который вы можете посмотреть на НДЦ видео страница .

6 голосов
/ 14 июля 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>());
1 голос
/ 14 июля 2010

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

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

0 голосов
/ 14 июля 2010

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

...