Relacionamentos de tipo em operações de consulta LINQ (C#)

Para escrever consultas com eficiência, você precisa entender como os tipos de variáveis em uma operação de consulta completa se relacionam entre si. Se você compreender esses relacionamentos, entenderá com mais facilidade os exemplos da LINQ e as amostras de código na documentação. Além disso, você entenderá o que ocorre quando as variáveis são digitadas implicitamente usando var.

As operações de consulta LINQ são fortemente tipadas na fonte de dados, na própria consulta e na execução da consulta. O tipo das variáveis na consulta deve ser compatível com o tipo dos elementos na fonte de dados e com o tipo da variável de iteração na instrução foreach. Essa tipagem forte garante que erros de tipo sejam capturados em tempo de compilação, quando podem ser corrigidos antes que os usuários os encontrem.

Para demonstrar essas relações de tipo, a maioria dos exemplos a seguir usam tipagem explícita para todas as variáveis. O último exemplo mostra como os mesmos princípios se aplicam mesmo quando você usa digitação implícita usando var.

Consultas que não transformam os dados de origem

A ilustração a seguir mostra uma operação de consulta LINQ to Objects que não executa transformações nos dados. A fonte contém uma sequência de cadeias de caracteres e a saída da consulta também é uma sequência de cadeias de caracteres.

Diagram that shows the relation of data types in a LINQ query.

  1. O argumento de tipo da fonte de dados determina o tipo da variável de intervalo.
  2. O tipo do objeto selecionado determina o tipo da variável de consulta. Esta é uma cadeia de caracteres name. Portanto, a variável de consulta é um IEnumerable<string>.
  3. A variável de consulta é iterada na instrução foreach. Como a variável de consulta é uma sequência de cadeias de caracteres, a variável de iteração também é uma cadeia de caracteres.

Consultas que transformam os dados de origem

A ilustração a seguir mostra uma operação de consulta LINQ to SQL que executa uma transformação simples nos dados. A consulta usa uma sequência de objetos Customer como entrada e seleciona somente a propriedade Name no resultado. Como Name é uma cadeia de caracteres, a consulta produz uma sequência de cadeias de caracteres como saída.

Diagram showing a query that transforms the data type.

  1. O argumento de tipo da fonte de dados determina o tipo da variável de intervalo.
  2. A instrução select retorna a propriedade Name em vez do objeto Customer completo. Como Name é uma cadeia de caracteres, o argumento de tipo de custNameQuery é string e não Customer.
  3. Como custNameQuery é uma sequência de cadeias de caracteres, a variável de iteração do loop foreach também deve ser um string.

A ilustração a seguir mostra uma transformação um pouco mais complexa. A instrução select retorna um tipo anônimo que captura apenas dois membros do objeto Customer original.

Diagram showing a more complex query that transforms the data type.

  1. O argumento de tipo da fonte de dados sempre é o tipo da variável de intervalo na consulta.
  2. Como a instrução select produz um tipo anônimo, a variável de consulta deve ser tipada implicitamente usando var.
  3. Como o tipo da variável de consulta é implícito, a variável de iteração no loop foreach também deve ser implícito.

Deixando o compilador inferir informações de tipo

Embora você precise entender as relações de tipo em uma operação de consulta, você tem a opção de permitir que o compilador fazer todo o trabalho. A palavra-chave var pode ser usada para qualquer variável local em uma operação de consulta. A ilustração a seguir é semelhante ao exemplo número 2 que foi discutido anteriormente. No entanto, o compilador fornece o tipo forte para cada variável na operação de consulta.

Diagram that shows the type flow with implicit typing.

LINQ e tipos genéricos (C#)

As consultas LINQ são baseadas em tipos genéricos. Não é necessário um conhecimento profundo sobre os genéricos antes de começar a escrever consultas. No entanto, convém entender dois conceitos básicos:

  1. Quando você cria uma instância de uma classe de coleção genérica, como List<T>, substitua o "T" pelo tipo dos objetos que a lista bloqueia. Por exemplo, uma lista de cadeias de caracteres é expressa como List<string> e uma lista de objetos Customer é expressa como List<Customer>. Uma lista genérica é fortemente tipada e oferece muitos benefícios em coleções que armazenam seus elementos como Object. Se tentar adicionar um Customer em uma List<string>, você obterá um erro em tempo de compilação. É fácil usar coleções genéricas, porque você não precisa realizar a conversão de tipo em tempo de execução.
  2. A IEnumerable<T> é a interface que permite que as classes de coleção genérica sejam enumeradas usando a instrução foreach. Classes de coleção genéricas dão suporte a IEnumerable<T> do mesmo modo que classes de coleção não genéricas, tais como ArrayList, dão suporte a IEnumerable.

Para obter mais informações sobre os genéricos, consulte Genéricos.

Variáveis IEnumerable<T> em consultas LINQ

As variáveis de consulta LINQ são do tipo IEnumerable<T> ou de um tipo derivado, como IQueryable<T>. Ao se deparar com uma variável de consulta que é tipada como IEnumerable<Customer>, significa apenas que a consulta, quando for executada, produzirá uma sequência de zero ou mais objetos 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}");
}

Permitir que o compilador manipule as declarações de tipo genérico

Se preferir, poderá evitar a sintaxe genérica, usando a palavra-chave var. A palavra-chave var instrui o compilador a inferir o tipo de uma variável de consulta, examinando a fonte de dados especificada na cláusula from. O exemplo a seguir produz o mesmo código compilado que o exemplo anterior:

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

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

A palavra-chave var é útil quando o tipo da variável for óbvio ou quando não é tão importante especificar explicitamente os tipos genéricos aninhados, como aqueles que são produzidos por consultas de grupo. É recomendável que você note que o código poderá se tornar mais difícil de ser lido por outras pessoas, caso você use a var. Para obter mais informações, consulte Variáveis locais de tipo implícito.