Linq - приведение IQueryable к IList возвращает ноль - ПОЧЕМУ? - PullRequest
2 голосов
/ 30 января 2009

Я должен упустить что-то очевидное здесь. Я не понимаю, почему это приведение результатов запроса linq возвращает ноль, а не типизированный список, который я запрашиваю.

IList<IMyDataInterface> list = query.ToList() as IList<IMyDataInterface>;

Полный код для запуска приведен ниже. Это пробел в знаниях, который мне нужно преодолеть. Я перепробовал все виды кастов, чтобы заставить его работать. Я не получаю никаких исключений, просто ноль. Следует отметить, что запрос Linq выбирает свои результаты в экземпляры моего пользовательского «MyDataClass», который реализует IMyDataInterface

class Program
{
    static void Main(string[] args)
    {
        IMyFunctionalInterface myObject = new MyClass();


        //myObject.Get() returns null for some reason...
        IList<IMyDataInterface> list = myObject.Get();

        Debug.Assert(list != null, "Cast List is null");
    }
}

public interface IMyFunctionalInterface
{
    IList<IMyDataInterface> Get();
}

public class MyClass : IMyFunctionalInterface
{
    public IList<IMyDataInterface> Get()
    {
        string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };

        var query = from n in names
                    where n.Contains("a")
                    select new MyDataClass
                    {
                        Name = n.ToString()
                    };

        //There IS data in the query result
        Debug.Assert(query != null, "result is null");
        //but the cast here makes it return null
        IList<IMyDataInterface> list = query.ToList() as IList<IMyDataInterface>;

        return list;
    }

}
public interface IMyDataInterface
{
    string Name { get; set; }
}

public class MyDataClass : IMyDataInterface
{
    public string Name { get; set; }
}

Ответы [ 2 ]

13 голосов
/ 30 января 2009

Проблема здесь одна из ковариация .

Во-первых, ваш пример слишком сложен. Я удалил немного пуха. Также я добавил несколько диагностических сообщений, освещающих проблему.

class Program
{
    static void Main(string[] args)
    {
        var names = new[] { "Tom", "Dick", "Harry", "Mary", "Jay" };

        var query = from n in names
                    select new C
                    {
                        S = n
                    };

        //There IS data in the query result
        Debug.Assert(query != null, "result is null");

        //but the conversion here makes it return null
        var list = query.ToList() as IList<I>;
        Console.WriteLine(query.ToList().GetType());

        // this assert fires.
        Debug.Assert(list != null, "Cast List is null");
    }
}

interface I
{
    string S { get; set; }
}

class C : I
{
    public string S { get; set; }
}

Вывод этой программы:

System.Collections.Generic.List`1[C]

Обратите внимание, что мы пытаемся привести List<C> к List<I>, который не работает в C # 3.0.

В C # 4.0 вы сможете это сделать благодаря новой совместной и противоположной дисперсии параметров типа на универсальных интерфейсах.

Кроме того, ваш первоначальный вопрос задавался по поводу IQueryable, но здесь это не имеет значения: предоставленное вами выражение запроса создает IEnumerable<string>, а не IQueryable<string>.

EDIT : Я хочу отметить, что ваше "приведение" с использованием оператора as технически не приведение, а преобразование типа. Если бы вы использовали приведение, вы бы получили исключение с полезной информацией. Если я изменю на:

    var list = (IList<I>)query.ToList();

Я получаю InvalidCastException с:

Additional information: Unable to cast object of type 'System.Collections.Generic.List1[C]' to type 'System.Collections.Generic.IList1[I]'.
5 голосов
/ 30 января 2009

Попробуйте это:

var query = from n in names
            where n.Contains("a")
            select new MyDataClass
            {
              Name = n.ToString()
            } as IMyDataInterface;

Ваша проблема в этой строке:

IList<IMyDataInterface> list = query.ToList() as IList<IMyDataInterface>;

Это также можно записать как:

List<MyDataClass> tmp = query.ToList();
IList<IMyDataInterface> list = tmp as IList<IMyDataInterface>;

К сожалению, в C # оператор as работает не так, как вы этого хотите. Оператор as просто приводит объект списка к списку другого типа; он не пытается пройти по списку и разыграть каждый предмет. Чтобы привести список к чему-то другому, вам нужно вызвать метод расширения Cast. E.g.:

IList<IMyDataInterface> list = query.ToList().Cast<IMyDataInterface>();

Итак, ваши варианты: приведите любой элемент в вашем запросе как нужный вам интерфейс (мой первый пример) или полный список после того, как вы выполнили свой запрос (мой второй пример)

Я предлагаю первый.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...