Compartilhar via


Visão geral do LINQ

Language-Integrated Query (LINQ) fornece recursos de consulta no nível da linguagem e uma função de ordem superior API para C# e Visual Basic, que permitem que você escreva código declarativo expressivo.

Sintaxe de consulta no nível do idioma

Esta é a sintaxe de consulta no nível do idioma:

var linqExperts = from p in programmers
                  where p.IsNewToLINQ
                  select new LINQExpert(p);
Dim linqExperts = From p in programmers
                  Where p.IsNewToLINQ
                  Select New LINQExpert(p)

Este é o mesmo exemplo usando a API IEnumerable<T>:

var linqExperts = programmers.Where(p => p.IsNewToLINQ)
                             .Select(p => new LINQExpert(p));
Dim linqExperts = programmers.Where(Function(p) p.IsNewToLINQ).
                             Select(Function(p) New LINQExpert(p))

LINQ é expressivo

Imagine que você tem uma lista de animais de estimação, mas deseja convertê-la em um dicionário onde você pode acessar um animal de estimação diretamente pelo seu valor RFID.

Esse é o código imperativo tradicional:

var petLookup = new Dictionary<int, Pet>();

foreach (var pet in pets)
{
    petLookup.Add(pet.RFID, pet);
}
Dim petLookup = New Dictionary(Of Integer, Pet)()

For Each pet in pets
    petLookup.Add(pet.RFID, pet)
Next

A intenção por trás do código não é criar um novo Dictionary<int, Pet> e adicioná-lo por meio de um loop, é converter uma lista existente em um dicionário! LINQ preserva a intenção, enquanto o código imperativo não.

Essa é a expressão LINQ equivalente:

var petLookup = pets.ToDictionary(pet => pet.RFID);
Dim petLookup = pets.ToDictionary(Function(pet) pet.RFID)

O código que usa LINQ é valioso porque nivela a intenção e o código quando se raciocina como programador. Outro bônus é a brevidade do código. Imagine reduzir grandes partes de uma base de código em 1/3, conforme feito acima. Doce negócio, certo?

Provedores LINQ simplificam o acesso a dados

Para uma parte significativa do software na natureza, tudo gira em torno de lidar com dados de alguma origem (Bancos de Dados, JSON, XML e assim por diante). Muitas vezes, isso envolve o aprendizado de uma nova API para cada fonte de dados, o que pode ser irritante. O LINQ simplifica isso abstraindo elementos comuns de acesso a dados em uma sintaxe de consulta que tenha a mesma aparência, independentemente da fonte de dados que você escolher.

Isso localiza todos os elementos XML com um valor de atributo específico:

public static IEnumerable<XElement> FindAllElementsWithAttribute(XElement documentRoot, string elementName,
                                           string attributeName, string value)
{
    return from el in documentRoot.Elements(elementName)
           where (string)el.Element(attributeName) == value
           select el;
}
Public Shared Function FindAllElementsWithAttribute(documentRoot As XElement, elementName As String,
                                           attributeName As String, value As String) As IEnumerable(Of XElement)
    Return From el In documentRoot.Elements(elementName)
           Where el.Element(attributeName).ToString() = value
           Select el
End Function

Escrever código para percorrer manualmente o documento XML para fazer essa tarefa seria muito mais desafiador.

Interagir com XML não é a única coisa que você pode fazer com provedores LINQ. O Linq to SQL é um ORM (Mapeador Relacional de Objetos) de funções bastante básicas para um banco de dados MSSQL. A biblioteca de Json.NET fornece uma passagem eficiente de documentos JSON através do LINQ. Além disso, se não existir uma biblioteca que faça o que você precisa, também é possível escrever seu próprio provedor LINQ.

Motivos para usar a sintaxe de consulta

Por que usar a sintaxe de consulta? Esta é uma pergunta que muitas vezes aparece. Afinal, o seguinte código:

var filteredItems = myItems.Where(item => item.Foo);
Dim filteredItems = myItems.Where(Function(item) item.Foo)

é muito mais conciso do que isso:

var filteredItems = from item in myItems
                    where item.Foo
                    select item;
Dim filteredItems = From item In myItems
                    Where item.Foo
                    Select item

A sintaxe da API não é apenas uma maneira mais concisa de fazer a sintaxe da consulta?

Não. A sintaxe de consulta permite o uso da cláusula let, que permite que você introduza e associe uma variável no escopo da expressão, usando-a em partes subsequentes da expressão. A reprodução do mesmo código com apenas a sintaxe da API pode ser feita, mas provavelmente levará a um código difícil de ler.

Portanto, isso levanta a questão, você deve usar apenas a sintaxe de consulta?

A resposta para essa pergunta é sim se:

  • Sua base de código existente já usa a sintaxe de consulta.
  • Você precisa definir o escopo de variáveis em suas consultas devido à complexidade.
  • Você prefere a sintaxe de consulta e ela não distrai sua base de código.

A resposta para essa pergunta será não se...

  • Sua base de código existente já usa a sintaxe da API
  • Você não precisa definir o escopo de variáveis em suas consultas
  • Você prefere a sintaxe da API e ela não distrai sua base de código

LINQ essencial

Os exemplos a seguir são uma demonstração rápida de algumas das partes essenciais do LINQ. Isso não é de forma alguma abrangente, pois o LINQ fornece mais funcionalidade do que o mostrado aqui.

O pão e a manteiga - Where, Selecte Aggregate

// Filtering a list.
var germanShepherds = dogs.Where(dog => dog.Breed == DogBreed.GermanShepherd);

// Using the query syntax.
var queryGermanShepherds = from dog in dogs
                          where dog.Breed == DogBreed.GermanShepherd
                          select dog;

// Mapping a list from type A to type B.
var cats = dogs.Select(dog => dog.TurnIntoACat());

// Using the query syntax.
var queryCats = from dog in dogs
                select dog.TurnIntoACat();

// Summing the lengths of a set of strings.
int seed = 0;
int sumOfStrings = strings.Aggregate(seed, (partialSum, nextString) => partialSum + nextString.Length);
' Filtering a list.
Dim germanShepherds = dogs.Where(Function(dog) dog.Breed = DogBreed.GermanShepherd)

' Using the query syntax.
Dim queryGermanShepherds = From dog In dogs
                          Where dog.Breed = DogBreed.GermanShepherd
                          Select dog

' Mapping a list from type A to type B.
Dim cats = dogs.Select(Function(dog) dog.TurnIntoACat())

' Using the query syntax.
Dim queryCats = From dog In dogs
                Select dog.TurnIntoACat()

' Summing the lengths of a set of strings.
Dim seed As Integer = 0
Dim sumOfStrings As Integer = strings.Aggregate(seed, Function(partialSum, nextString) partialSum + nextString.Length)

Nivelar uma lista de listas

// Transforms the list of kennels into a list of all their dogs.
var allDogsFromKennels = kennels.SelectMany(kennel => kennel.Dogs);
' Transforms the list of kennels into a list of all their dogs.
Dim allDogsFromKennels = kennels.SelectMany(Function(kennel) kennel.Dogs)

União entre dois conjuntos (com comparador personalizado)

public class DogHairLengthComparer : IEqualityComparer<Dog>
{
    public bool Equals(Dog a, Dog b)
    {
        if (a == null && b == null)
        {
            return true;
        }
        else if ((a == null && b != null) ||
                 (a != null && b == null))
        {
            return false;
        }
        else
        {
            return a.HairLengthType == b.HairLengthType;
        }
    }

    public int GetHashCode(Dog d)
    {
        // Default hashcode is enough here, as these are simple objects.
        return d.GetHashCode();
    }
}
...

// Gets all the short-haired dogs between two different kennels.
var allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, new DogHairLengthComparer());
Public Class DogHairLengthComparer
  Inherits IEqualityComparer(Of Dog)

  Public Function Equals(a As Dog,b As Dog) As Boolean
      If a Is Nothing AndAlso b Is Nothing Then
          Return True
      ElseIf (a Is Nothing AndAlso b IsNot Nothing) OrElse (a IsNot Nothing AndAlso b Is Nothing) Then
          Return False
      Else
          Return a.HairLengthType = b.HairLengthType
      End If
  End Function

  Public Function GetHashCode(d As Dog) As Integer
      ' Default hashcode is enough here, as these are simple objects.
      Return d.GetHashCode()
  End Function
End Class

...

' Gets all the short-haired dogs between two different kennels.
Dim allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, New DogHairLengthComparer())

Interseção entre dois conjuntos

// Gets the volunteers who spend share time with two humane societies.
var volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
                                                     new VolunteerTimeComparer());
' Gets the volunteers who spend share time with two humane societies.
Dim volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
                                                     New VolunteerTimeComparer())

Pedido

// Get driving directions, ordering by if it's toll-free before estimated driving time.
var results = DirectionsProcessor.GetDirections(start, end)
              .OrderBy(direction => direction.HasNoTolls)
              .ThenBy(direction => direction.EstimatedTime);
' Get driving directions, ordering by if it's toll-free before estimated driving time.
Dim results = DirectionsProcessor.GetDirections(start, end).
                OrderBy(Function(direction) direction.HasNoTolls).
                ThenBy(Function(direction) direction.EstimatedTime)

Igualdade de propriedades de instância

Por fim, um exemplo mais avançado: determinar se os valores das propriedades de duas instâncias do mesmo tipo são iguais (emprestado e modificado de esta postagem no StackOverflow):

public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
    if (self == null || to == null)
    {
        return self == to;
    }

    // Selects the properties which have unequal values into a sequence of those properties.
    var unequalProperties = from property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                            where !ignore.Contains(property.Name)
                            let selfValue = property.GetValue(self, null)
                            let toValue = property.GetValue(to, null)
                            where !Equals(selfValue, toValue)
                            select property;
    return !unequalProperties.Any();
}
<System.Runtime.CompilerServices.Extension()>
Public Function PublicInstancePropertiesEqual(Of T As Class)(self As T, [to] As T, ParamArray ignore As String()) As Boolean
    If self Is Nothing OrElse [to] Is Nothing Then
        Return self Is [to]
    End If

    ' Selects the properties which have unequal values into a sequence of those properties.
    Dim unequalProperties = From [property] In GetType(T).GetProperties(BindingFlags.Public Or BindingFlags.Instance)
                            Where Not ignore.Contains([property].Name)
                            Let selfValue = [property].GetValue(self, Nothing)
                            Let toValue = [property].GetValue([to], Nothing)
                            Where Not Equals(selfValue, toValue) Select [property]
    Return Not unequalProperties.Any()
End Function

PLINQ

PLINQ ou PARALLEL LINQ é um mecanismo de execução paralelo para expressões LINQ. Em outras palavras, uma expressão LINQ regular pode ser paralelizada trivialmente em qualquer número de threads. Isso é feito por meio de uma chamada para AsParallel() antes da expressão.

Considere o seguinte:

public static string GetAllFacebookUserLikesMessage(IEnumerable<FacebookUser> facebookUsers)
{
    var seed = default(UInt64);

    Func<UInt64, UInt64, UInt64> threadAccumulator = (t1, t2) => t1 + t2;
    Func<UInt64, UInt64, UInt64> threadResultAccumulator = (t1, t2) => t1 + t2;
    Func<Uint64, string> resultSelector = total => $"Facebook has {total} likes!";

    return facebookUsers.AsParallel()
                        .Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector);
}
Public Shared GetAllFacebookUserLikesMessage(facebookUsers As IEnumerable(Of FacebookUser)) As String
{
    Dim seed As UInt64 = 0

    Dim threadAccumulator As Func(Of UInt64, UInt64, UInt64) = Function(t1, t2) t1 + t2
    Dim threadResultAccumulator As Func(Of UInt64, UInt64, UInt64) = Function(t1, t2) t1 + t2
    Dim resultSelector As Func(Of Uint64, string) = Function(total) $"Facebook has {total} likes!"

    Return facebookUsers.AsParallel().
                        Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector)
}

Esse código particionará facebookUsers entre threads do sistema conforme necessário, somará o total de curtidas em cada thread em paralelo, somará os resultados computados por cada thread e projetará esse resultado em uma string agradável.

Em formato de diagrama:

diagrama PLINQ

Trabalhos paralelizáveis associados à CPU que podem ser facilmente expressos via LINQ (em outras palavras, são funções puras e não têm efeitos colaterais) são um ótimo candidato para PLINQ. Para trabalhos que têm um efeito colateral, considere usar a Biblioteca de paralelismo de tarefas.

Mais recursos

  • Linqpad, um mecanismo de consulta de banco de dados e ambiente de playground para C#/F#/Visual Basic
  • EduLinq, um e-book para aprender como o LINQ-to-objects é implementado