1. Introdução
Quando você assiste uma série, nem sempre descobre tudo sobre todos os personagens, só o que importa pra história. Em programação, rola a mesma coisa: às vezes a gente não precisa do objeto inteiro, só de alguns campos, valores ou até de uns cálculos em cima deles.
Projeção no LINQ é transformar os elementos da sequência original em uma nova forma. Normalmente usando o método Select. Dá pra pensar no Select como uma máquina mágica que pega cada elemento, aplica uma função e devolve o resultado. Só isso, sem truque.
Quando usar projeção?
- Queremos mostrar só os nomes dos usuários, não todos os dados deles.
- Preparando um email marketing: do objeto cliente pegamos só o endereço e o nome.
- Fazendo um cálculo: adicionamos imposto ao preço do produto e criamos uma nova coleção.
- Pra UI: precisa mostrar só parte dos dados, sem sobrecarregar a interface.
2. Método Select: sintaxe e uso básico
Sintaxe principal
LINQ aceita dois estilos: sintaxe de método (Method Syntax) e sintaxe de consulta (Query Syntax). Pro Select, o resultado é igual nos dois.
Sintaxe de método
var query = myCollection.Select(x => x.OQueRetornar);
Aqui, x é a variável pra cada elemento da coleção, e à direita vai a expressão que transforma o elemento no que você quer.
Sintaxe de consulta
var query = from x in myCollection
select x.OQueRetornar;
Aqui fica mais parecido com SQL.
Exemplo simples: lista de números
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Quero pegar os quadrados deles
var squares = numbers.Select(n => n * n);
foreach (var s in squares)
{
Console.WriteLine(s); // 1, 4, 9, 16, 25
}
Olha só, a gente transformou a coleção de números numa coleção dos quadrados deles — rapidinho, sem precisar de loop manual!
Exemplo 2
Nos exemplos anteriores já tinha a classe Product:
public class Product
{
public string Name { get; set; }
public double Price { get; set; }
}
Imagina que temos uma lista:
var products = new List<Product>
{
new Product { Name = "Chocolate", Price = 120 },
new Product { Name = "Queijo", Price = 250 },
new Product { Name = "Pão", Price = 55 }
};
Se a gente só quer os nomes dos produtos, não precisa do objeto inteiro, né? Só a lista dos nomes:
var names = products.Select(p => p.Name);
foreach (var name in names)
{
Console.WriteLine(name); // Chocolate, Queijo, Pão
}
3. Coleção retornada — quais são as opções?
Repara que o Select sempre devolve uma coleção (na real — IEnumerable<TOutput>), onde TOutput é o tipo do resultado da função. Você escolhe o que vai ser: string, número, tipo anônimo, até um objeto novo.
Projeção pra um novo tipo
Por exemplo, você quer pegar não o objeto cru, mas a "projeção" dele:
var projections = products.Select(p => new { p.Name, PrecoComImposto = p.Price * 1.2 });
foreach (var item in projections)
{
Console.WriteLine($"{item.Name}: {item.PrecoComImposto}");
}
Aqui a gente criou uma nova coleção de objetos anônimos — com nome e preço já com um imposto imaginário!
4. Retornando uma nova classe ou tipo anônimo
Você pode escolher: criar classes completas pro resultado ou só usar tipos anônimos, se a estrutura só vai ser usada "aqui e agora".
Tipos anônimos
var result = products.Select(p => new { Nome = p.Name, PrecoAntigo = p.Price, PrecoNovo = p.Price + 40 });
foreach (var p in result)
{
Console.WriteLine($"{p.Nome}: Era {p.PrecoAntigo}, agora {p.PrecoNovo}");
}
Tipos anônimos são ótimos pra montar resultados rapidinho, principalmente quando não vale a pena criar uma classe só pra uma operação.
Usando uma classe própria pra projeção
Vamos criar uma nova classe:
public class ProductDto
{
public string Name { get; set; }
public double PriceWithTax { get; set; }
}
Agora dentro do LINQ:
var productsWithTax = products.Select(p => new ProductDto
{
Name = p.Name,
PriceWithTax = p.Price * 1.2
});
foreach (var p in productsWithTax)
{
Console.WriteLine($"{p.Name}: {p.PriceWithTax}");
}
5. Acessando coleções e propriedades internas
E se dentro do objeto tem uma coleção? Tipo, cada usuário tem uma lista de compras.
public class User
{
public string Name { get; set; }
public List<Product> Purchases { get; set; }
}
Quer pegar a lista de todas as listas de compras? Suave!
var allPurchases = users.Select(u => u.Purchases);
foreach (var purchaseList in allPurchases)
{
foreach (var product in purchaseList)
{
Console.WriteLine(product.Name);
}
}
Importante! Nesse caso, a gente ficou com uma coleção de coleções, não uma lista "plana" de produtos. Como pegar UMA lista com todos os produtos de todos os usuários? Isso é assunto pra próxima aula — lá vamos conhecer o SelectMany.
6. Dicas úteis
Converter pra tipo de coleção: ToList() e afins
Select devolve um IEnumerable<T>. Se você quiser uma lista normal (List<T>), só mandar um .ToList():
var nameList = products.Select(p => p.Name).ToList();
Agora você tem uma lista de verdade, pronta pra usar do jeito que quiser.
Usando o índice do elemento
Às vezes é útil saber qual elemento da coleção tá sendo processado. O método Select tem uma sobrecarga:
var indexed = products.Select((product, index) => new { Index = index, Name = product.Name });
foreach (var item in indexed)
{
Console.WriteLine($"{item.Index}: {item.Name}");
}
Dá pra, por exemplo, preparar dados pra listas numeradas.
Projeção na programação
Saber usar Select é pergunta clássica em entrevista. Sem isso, nem rola imaginar apps modernos:
- Pegar só os dados necessários do banco ou de serviços externos.
- Preparar dados pra passar entre camadas do app.
- Transformar dados pra mostrar na tela ou mandar pela rede.
- Economizar memória (não carrega o objeto inteiro, só os campos que precisa).
Empresas que lidam com muitos dados geralmente montam a lógica de negócio em cima disso.
7. Erros comuns e detalhes
Às vezes quem tá começando se depara com bugs chatos — normalmente porque não entendeu como funciona a execução preguiçosa (deferred execution) no LINQ. Por exemplo, depois de chamar Select nem sempre o resultado é calculado na hora — só quando você realmente itera a coleção (foreach, ToList(), etc). Isso deixa montar um "pipeline de processamento", mas às vezes faz o resultado mudar se os dados originais mudaram antes de usar o resultado da consulta.
Também não esquece: Select não muda a coleção original — ele cria uma nova. Então se você espera achar novos campos em products depois de products.Select(...), não vai rolar.
Tratando null: se a projeção pode acessar campos que podem ser null, fica ligado nisso. No C# 8+ o compilador já avisa se pode rolar um NullReferenceException.
GO TO FULL VERSION