Поделиться через


Связи типов в операциях запросов 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.

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

Переменные 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, понимать, что это может сделать код более сложным для других пользователей для чтения. Дополнительные сведения см. в разделе Неявно типизированные локальные переменные.