Depois de todo este trabalho chegamos ao ponto em que eu digo: Tudo isto é desnecessário porque o Linq já nos oferece as três high-order functions em sua implementação.
Calma, pequeno gafanhoto, muitas vezes o caminho é muito mais interessante que o destino em si.
Se eu simplesmente mostrasse estas funcionalidades do Linq tudo pareceria confuso e mágico. Agora, sabendo como funcionam as high-order functions, fica muito mais fácil de entender o que está acontecendo.
Como falei antes algumas pessoas acham a sintaxe Linq sexy, outros não gostam e tem dificuldade de ler o código. A boa notícia é que não é necessário utilizar aquela sintaxe. Nos exemplos abaixo vou utilizar a API do Linq, ao invés da sintaxe de query.
Filter
Veja como é fácil filtrar uma coleção de objetos utilizando o Linq:
List<Cliente> clientesDoPara = clientes.Where(cliente => cliente.Estado.Equals("Pará"))
.ToList();
Utilizando a sintaxe Linq:
var clientesDoPara = (from cliente in clientes
where cliente.Estado.Equals("Pará")
select cliente).ToList();
Map
Para o map posso utilizar a função select, também sem problemas.
var clientes = clientesCrm.Select(clienteCRM => new Cliente(string.Format("{0} {1}",
clienteCRM.FirstName,
clienteCRM.LastName)))
.ToList();
Uitlizando a sintaxe Linq:
var clientes = (from clienteCRM in clientesCrm
select new Cliente(string.Format("{0} {1}",
clienteCRM.FirstName,
clienteCRM.LastName))
).ToList();
Fold ou Reduce
Este é um pouco mais interessante. Out-of-the-box o Linq já oferece várias implementações específicas, por exemplo Sum, Max, Min. São todos especializações do Reduce. Ele também oferece o genérico, Aggregate, que aceita a função que realiza o fold. Em nosso exemplo o Sum não nos atende, precisamos somar a multiplicação de dois campos. O resultado ficaria assim:
public double TotalAmount()
{
return lines.Aggregate<SalesLine, double>(0d, AcumulaTotalDaLinha);
}
private static double AcumulaTotalDaLinha(double total, SalesLine item)
{
return total + (item.Quantidade * item.Preco);
}
Mas também posso refatorar meu código e passar o cálculo do total da linha para a própria linha, faz todo o sentido, por sinal, neste caso posso utilizar o Sum:
public class SalesLine
{
public double Quantidade { get; set; }
public double Preco { get; set; }
public double Total()
{
return this.Quantidade * this.Preco;
}
}
public double TotalAmount()
{
return lines.Select(line => line.Total())
.Sum();
}
}
Ou simplesmente:
public double TotalAmount()
{
return lines.Sum(line => line.Total());
}
Perfomance e escopo
Olhando para os exemplos desta série uma importante dúvida pode surgir: Mas eu preciso transformar TUDO em coleção antes realizar estas operações? Por exemplo, não seria mais performático realizar o filtro ao trazer a coleção do banco de dados ao invés de realizá-la na coleção? O mesmo pode se dizer da integração, é necessário trazer todos os dados, criar uma coleção para depois transformá-los?
Para responder isto é necessário diferenciar o que é Linq do que são implementações de Linq:
Linq é a .Net Language-integrated query, ou seja, a sintaxe, e a API, que permite realizar operações sobre objetos IEnumerable. “Apenas” isto.
Uma implementação de Linq implementa estas operações para um determinado escopo. A Microsoft lançou, junto com o Linq, algumas implementações:
- Linq to objects – Permite realizar queries Linq em qualquer coleção do .Net framework que implementa o IEnumerable, como List ou Array. É o que estou utilizando neste exemplo e o que você vê na imensa maioria de exemplos na internet.
- Linq to SQL – Permite realizar queries Linq em Banco de dados SQL Server. Para possibilitar isto ele realiza um mapeamento entre tabelas do banco de dados e objetos .Net. As queries realizadas nestes objetos são transformadas em comandos T-SQL e executados no Banco de dados, retornando objetos instanciados.
- Linq to XML – Permite realizar queries Linq em arquivos XML. Para isto foi criada uma nova API de manipulação de XML, localizada em System.Xml.Linq
Além destes existem diversas implementações de Linq criadas por terceiros. Desde “brincadeiras” como o Linq to Flickr até implementações para ferramentas de ORM. Uma ótima leitura para quem quer entender melhor isto é a série do Frans Bouma sobre a implementação do Linq to LLBLGen.
Então, respondendo a pergunta original: Não, o Linq não é restrito a coleções de objetos .Net, você pode fazer as mesmas queries em um Banco de dados SQL Server, por exemplo.
Quanto a performance ela não depende do Linq mas da implementação. Eu posso fazer um Linq to AnySGBD que simplesmente traz todas as linhas da tabelas e realiza o filtro em .Net, certamente não será a implementação mais performática disponível :)