Sintaxe de consulta e sintaxe de método em LINQ (C#)

A maioria das consultas na documentação introdutória do LINQ (Consulta Integrada à Linguagem) é escrita com o uso da sintaxe de consulta declarativa do LINQ. No entanto, a sintaxe de consulta deve ser convertida em chamadas de método para o CLR (Common Language Runtime) do .NET quando o código for compilado. Essas chamadas de método invocam os operadores de consulta padrão, que têm nomes como Where, Select, GroupBy, Join, Max e Average. Você pode chamá-los diretamente usando a sintaxe de método em vez da sintaxe de consulta.

A sintaxe de consulta e a sintaxe de método são semanticamente idênticas, mas muitas pessoas acham a sintaxe de consulta mais simples e fácil de ler. Algumas consultas devem ser expressadas como chamadas de método. Por exemplo, você deve usar uma chamada de método para expressar uma consulta que recupera o número de elementos que correspondem a uma condição especificada. Você também deve usar uma chamada de método para uma consulta que recupera o elemento que tem o valor máximo em uma sequência de origem. A documentação de referência para os operadores de consulta padrão no namespace System.Linq geralmente usa a sintaxe de método. Portanto, mesmo ao começar a escrever consultas LINQ, é útil que você esteja familiarizado com a forma de uso da sintaxe de método em consultas e nas próprias expressões de consulta.

Métodos de Extensão do Operador de Consulta Padrão

O exemplo a seguir mostra uma expressão de consulta simples e a consulta semanticamente equivalente escrita como uma consulta baseada em método.

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
 */

A saída dos dois exemplos é idêntica. Você pode ver que o tipo da variável de consulta é o mesmo em ambas as formas: IEnumerable<T>.

Para entender a consulta baseada em método, vamos examiná-la melhor. No lado direito da expressão, observe que a cláusula where agora é expressa como um método de instância no objeto numbers, que, como você deve se lembrar, tem um tipo de IEnumerable<int>. Se você estiver familiarizado com a interface IEnumerable<T> genérica, você saberá que ela não tem um método Where. No entanto, se você invocar a lista de conclusão do IntelliSense no IDE do Visual Studio, verá não apenas um método Where, mas muitos outros métodos como Select, SelectMany, Join e Orderby. Esses são todos os operadores de consulta padrão.

Captura de tela que mostra todos os operadores de consulta padrão no IntelliSense.

Embora pareça como se IEnumerable<T> tivesse sido redefinido para incluir esses métodos adicionais, na verdade esse não é o caso. Os operadores de consulta padrão são implementados como um novo tipo de método chamado métodos de extensão. Métodos de extensão "estendem" um tipo existente, eles podem ser chamados como se fossem métodos de instância no tipo. Os operadores de consulta padrão estendem IEnumerable<T> e que é por esse motivo que você pode escrever numbers.Where(...).

Para começar a usar LINQ, tudo o que você realmente precisa saber sobre os métodos de extensão é como colocá-los no escopo no seu aplicativo usando as diretivas using corretas. Do ponto de vista do aplicativo, um método de extensão e um método de instância normal são iguais.

Para obter mais informações sobre os métodos de extensão, consulte Métodos de extensão. Para obter mais informações sobre os operadores de consulta padrão, consulte Visão geral de operadores de consulta padrão (C#). Alguns provedores LINQ, como LINQ to SQL e LINQ to XML, implementam seus próprios operadores de consulta padrão e métodos de extensão adicionais para outros tipos além de IEnumerable<T>.

Expressões lambda

No exemplo anterior, observe que a expressão condicional (num % 2 == 0) é passada como um argumento embutido para o método Where: Where(num => num % 2 == 0). Essa expressão embutida é chamada de uma expressão lambda. É uma maneira conveniente de escrever um código que de outra forma precisaria ser escrito de forma mais complicada como um método anônimo, um delegado genérico ou uma árvore de expressão. No C# => é o operador lambda, que é lido como "vai para". O num à esquerda do operador é a variável de entrada que corresponde ao num na expressão de consulta. O compilador pode inferir o tipo de num porque ele sabe que numbers é um tipo IEnumerable<T> genérico. O corpo do lambda é exatamente igual à expressão na sintaxe de consulta ou em qualquer outra expressão ou instrução C#, ele pode incluir chamadas de método e outra lógica complexa. O "valor retornado" é apenas o resultado da expressão.

Para começar a usar o LINQ, você não precisa usar lambdas extensivamente. No entanto, determinadas consultas só podem ser expressadas em sintaxe de método e algumas delas requerem expressões lambda. Após se familiarizar com lambdas, você verá que eles são uma ferramenta avançada e flexível na sua caixa de ferramentas do LINQ. Para obter mais informações, consulte Expressões Lambda.

Possibilidade de Composição das Consultas

No exemplo de código anterior, observe que o método OrderBy é invocado usando o operador ponto na chamada para Where. Where produz uma sequência filtrada e, em seguida, Orderby opera nessa sequência classificando-a. Como as consultas retornam uma IEnumerable, você pode escrevê-las na sintaxe de método encadeando as chamadas de método. Isso é o que o compilador faz nos bastidores quando você escreve consultas usando a sintaxe de consulta. E como uma variável de consulta não armazena os resultados da consulta, você pode modificá-la ou usá-la como base para uma nova consulta a qualquer momento, mesmo depois que ela foi executada.