Отношения между типами в операциях запросов LINQ (C#)

Для эффективного написания запросов следует понимать, как типы переменных связаны друг с другом в полной операции запроса. В таком случае вам будет проще работать с примерами LINQ и примерами кода в документации. Кроме того, вы узнаете, что происходит при неявном типе переменных с помощью var.

Операции запросов LINQ строго типизированы в источнике данных, в самом запросе и при выполнении запроса. Тип переменных в запросе должен быть совместим с типом элементов в источнике данных и с типом переменной итерации в операторе foreach. Строгая типизация гарантирует перехват ошибок во время компиляции, когда их можно будет исправить прежде, чем с ними столкнутся пользователи.

Для демонстрации связи типов большая часть примеров использует явную типизацию для всех переменных. В последнем примере показано, как применяются те же принципы даже при использовании неявного ввода с помощью var.

Запросы, не выполняющие преобразование исходных данных

На следующем рисунке показана операция запроса LINQ to Objects, не выполняющая преобразование данных. Источник содержит последовательность строк, результат запроса также является последовательностью строк.

Схема, показывающая отношения между типами данных в запросе LINQ.

  1. Аргумент типа источника данных определяет тип переменной диапазона.
  2. Тип выбранного объекта определяет тип переменной запроса. Здесь name является строкой. Следовательно, переменная запроса представляет собой IEnumerable<string>.
  3. Итерация переменной запроса выполняется в операторе foreach. Поскольку переменная запроса является последовательностью строк, переменная итерации также является строкой.

Запросы, выполняющие преобразование исходных данных

На следующем рисунке показана операция запроса LINQ to SQL, которая выполняет простое преобразование данных. В качестве входных данных запрос получает последовательность объектов Customer и выбирает в результате только свойство Name. Поскольку Name является строкой, запрос формирует последовательность строк в качестве выходных данных.

Схема, показывающая запрос, преобразующий тип данных.

  1. Аргумент типа источника данных определяет тип переменной диапазона.
  2. Оператор select возвращает свойство Name вместо целого объекта Customer. Поскольку Name является строкой, аргумент типа custNameQuery является string, а не Customer.
  3. Поскольку custNameQuery представляет собой последовательность строк, переменная итерации цикла foreach также должна быть string.

На следующем рисунке показано немного более сложное преобразование. Оператор select возвращает анонимный тип, захватывающий только два члена исходного объекта Customer.

Схема, показывающая более сложный запрос, преобразующий тип данных.

  1. Аргумент типа источника данных всегда является типом переменной диапазона в запросе.
  2. Так как оператор select создает анонимный тип, переменная запроса должна неявно типизирована с помощью var.
  3. Поскольку тип переменной запроса неявный, переменная итерации в цикле foreach также должна быть неявной.

Разрешение компилятору определять сведения о типе

Несмотря на то, что необходимо обладать знаниями об отношениях типов в операции запроса, существует возможность передачи выполнения всех действий компилятору. Ключевое слово var можно использовать для любой локальной переменной в операции запроса. Следующий рисунок похож на пример 2, рассмотренный выше. Однако компилятор предоставляет строгий тип для каждой переменной в операции запроса.

Схема, показывающая поток для типа с неявной типизацией.

LINQ и универсальные типы (C#)

Запросы LINQ основаны на универсальных типах. Для того чтобы приступить к написанию запросов, не требуется глубокое знание универсальных типов. Тем не менее необходимо понимание двух основных принципов.

  1. При создании экземпляра универсального класса коллекции, такого как List<T>, замените "T" типом объектов, которые будут храниться в списке. Например, список строк выражается как List<string>, а список объектов Customer — как List<Customer>. Универсальный список является строго типизированным и предоставляет ряд преимуществ по сравнению с коллекциями, которые хранят свои элементы как Object. При попытке добавить Customer в List<string> будет выдана ошибка во время компиляции. Универсальные коллекции просты в использовании, поскольку нет необходимости выполнять приведение типов во время выполнения.
  2. IEnumerable<T> — это интерфейс, поддерживающий перечисление универсальных классов коллекций с помощью оператора foreach. Универсальные классы коллекций поддерживают IEnumerable<T> так же, как неуниверсальные классы коллекций (например ArrayList) поддерживают IEnumerable.

Дополнительные сведения об универсальных классах см. в разделе Универсальные шаблоны.

Переменные IEnumerable<T> в запросах LINQ

Переменные запроса LINQ типизированы как IEnumerable<T> или производный тип, например IQueryable<T>. Если появляется переменная запроса, которая типизируется как IEnumerable<Customer>, это просто означает, что запрос при выполнении выведет последовательность из нуля или более объектов Customer.

IEnumerable<Customer> customerQuery =
    from cust in customers
    where cust.City == "London"
    select cust;

foreach (Customer customer in customerQuery)
{
    Console.WriteLine($"{customer.LastName}, {customer.FirstName}");
}

Предоставление компилятору обработки объявлений универсального типа

При желании можно отказаться от обычного синтаксиса универсальных типов, используя ключевое слово var. Ключевое слово var дает компилятору указание вывести тип переменной запроса путем поиска в источнике данных, указанном в предложении from. В следующем примере создается тот же скомпилированный код, что и в предыдущем примере:

var customerQuery2 =
    from cust in customers
    where cust.City == "London"
    select cust;

foreach(var customer in customerQuery2)
{
    Console.WriteLine($"{customer.LastName}, {customer.FirstName}");
}

Ключевое слово var удобно, если тип переменной является очевидным, или если не требуется явно указывать вложенные универсальные типы, например создаваемые групповыми запросами. Как правило, рекомендуется помнить о том, что использование var делает код более сложным для чтения. Дополнительные сведения см. в разделе Неявно типизированные локальные переменные.