Синтаксис запросов и синтаксис методов в LINQ (C#)

Большинство запросов в вводной документации к LINQ написано с использованием декларативного синтаксиса запросов LINQ. Однако синтаксис запроса должен быть преобразован в вызовы методов для среды CLR .NET, когда код компилируется. Эти вызовы метода вызывают стандартные операторы запросов, которые имеют такие имена, как Where, Select, GroupBy, Join, Max и Average. Вместо синтаксиса запросов для их вызова можно использовать синтаксис методов.

Синтаксис запросов и синтаксис методов семантически идентичны, но многие пользователи найдут синтаксис запросов более простым и более удобным для чтения. Некоторые запросы должны быть выражены как вызовы методов. Например, необходимо использовать вызов метода для выражения запроса, который возвращает число элементов, соответствующих указанным критериям. Вызов метода также необходимо использовать для запроса, который получает элемент с максимальным значением в исходной последовательности. В справочной документации по стандартным операторам запросов в пространствах имен System.Linq обычно применяется синтаксис методов. В связи с этим даже на начальном этапе работы с запросами LINQ полезно иметь представление о том, как использовать синтаксис методов в самих запросах и выражениях запросов.

Методы расширения стандартных операторов запросов

В следующем примере показано простое выражение запроса и семантически эквивалентный ему запрос, написанный как запрос, основанный на методе.

class QueryVMethodSyntax
{
    static void Main()
    {
        int[] numbers = { 5, 10, 8, 3, 6, 12};

        //Query syntax:
        IEnumerable<int> numQuery1 =
            from num in numbers
            where num % 2 == 0
            orderby num
            select num;

        //Method syntax:
        IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);

        foreach (int i in numQuery1)
        {
            Console.Write(i + " ");
        }
        Console.WriteLine(System.Environment.NewLine);
        foreach (int i in numQuery2)
        {
            Console.Write(i + " ");
        }

        // Keep the console open in debug mode.
        Console.WriteLine(System.Environment.NewLine);
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}
/*
    Output:
    6 8 10 12
    6 8 10 12
 */

Оба примера дают одинаковый результат. Видно, что тип переменной запроса в обеих формах одинаковый: IEnumerable<T>.

Чтобы разобраться в запросе, основанном на методе, изучим его более подробно. Обратите внимание на то, что в правой части выражения предложение where теперь выражается как метод экземпляра в объекта numbers, который, как вы, наверное, помните, имеет тип IEnumerable<int>. Если вы знакомы с универсальным интерфейсом IEnumerable<T>, то знаете, что в нем отсутствует метод Where. При этом, вызвав список завершения IntelliSense в Visual Studio IDE, вы увидите не только метод Where, но и многие другие методы, такие как Select, SelectMany, Join и Orderby. Все это — стандартные операторы запросов.

Снимок экрана, показывающий все стандартные операторы запросов в Intellisense.

Может показаться, что класс IEnumerable<T> был переопределен и включает дополнительные методы, однако это не так. Стандартные операторы запросов реализуются как новый тип метода, который называется методы расширения. Эти методы "расширяют" существующий тип и могут вызываться так, как если бы они являлись методами экземпляра для этого типа. Стандартные операторы запроса расширяют IEnumerable<T>, и поэтому вы можете написать numbers.Where(...).

Чтобы приступить к использованию LINQ, о методах расширения достаточно знать только то, как ввести их в область действия в приложении, используя директивы using. С точки зрения приложения метод расширения и обычные методы экземпляров одинаковы.

Дополнительные сведения о методах расширения см. в разделе Методы расширения. Дополнительные сведения о стандартных операторах запросов см. в разделе Общие сведения о стандартных операторах запроса (C#). Некоторые поставщики LINQ, такие как LINQ to SQL и LINQ to XML, помимо IEnumerable<T> реализуют собственные стандартные операторы запросов и дополнительные методы расширения для других типов.

Лямбда-выражения

В предыдущем примере обратите внимание на то, что условное выражение (num % 2 == 0) передается в метод Where как встроенный аргумент: Where(num => num % 2 == 0).. Это встроенное выражение называется лямбда-выражением. Это удобный способ написания кода, который иначе пришлось бы записывать более громоздко: как анонимный метод, универсальный метод-делегат или дерево выражения. В C# => представляет собой лямбда-оператор, который читается как "переходит в". num слева от оператора — входная переменная, которая соответствует переменной num в выражении запроса. Компилятор может вывести тип num, поскольку известно, что numbers является универсальным типом IEnumerable<T>. Тело лямбда-выражения — точно такое же, как выражение в синтаксисе запроса или в любом другом выражении или операторе C#, и может включать вызовы метода и другие сложные логические выражения. "Возвращаемое значение" — результат выражения.

Чтобы приступить к использованию LINQ, активно использовать лямбда-выражения необязательно. При этом одни запросы могут быть выражены с помощью синтаксиса запроса, в то время как другие требуют лямбда-выражений. После знакомства с лямбда-выражениями станет понятно, что они являются мощными и гибкими элементами в арсенале элементов LINQ. Дополнительные сведения см. в разделе Лямбда-выражения.

Совместимость запросов

Обратите внимание на то, что в представленном выше примере кода метод OrderBy вызывается с помощью оператора точки при вызове Where. Where создает отфильтрованную последовательность, а затем Orderby ее сортирует. Поскольку запросы возвращают IEnumerable, объедините их в синтаксис метода, собрав вызовы методов в цепочку. Компилятор выполняет это действие в фоновом режиме, когда вы пишете запросы, используя синтаксис запросов. А поскольку в переменной запроса результаты запроса не сохраняются, его можно в любое время изменить или использовать как базу для нового запроса даже после выполнения.