Select() transforma cada elemento em um novo elemento. SelectMany() transforma cada elemento em uma sequência de elementos e depois concatena essas sequências em um único resultado plano.
Essa diferença aparece na contagem. Dez departamentos passados por Select() geram dez resultados. Dez departamentos passados por SelectMany() geram todos os funcionários de todos eles. Então, a pergunta a fazer nunca é qual método é melhor. É se aquilo que a projeção produz é um valor ou vários.
Como funciona o método Select
Quando falamos de LINQ (Language Integrated Query) em C#, o método Select() é uma das primeiras ferramentas que nos vêm à cabeça.
Podemos usar uma das sobrecargas disponíveis:
public static IEnumerable<TResult> Select<TSource,TResult> (this IEnumerable<TSource> source, Func<TSource,TResult> selector);
Esse método de extensão aceita a função de transformação com um único parâmetro, que é o elemento da coleção. É o método que a maioria de nós, desenvolvedores C#, usa todos os dias.
Como alternativa, podemos usar a outra sobrecarga:
public static IEnumerable<TResult> Select<TSource,TResult> (this IEnumerable<TSource> source, Func<TSource,int,TResult> selector);
Desta vez, o método aceita a função de transformação com dois parâmetros.
O segundo parâmetro representa o índice do elemento na coleção. Isso pode ser útil nos casos em que queremos usar o índice do elemento na função de transformação.
Os dois métodos são uma parte fundamental do LINQ e nos permitem fazer projeções sobre os dados.
Projeção de dados simplificada
No contexto do LINQ, projeção é a transformação de cada elemento de uma coleção em uma nova forma.
É isso que o método Select() faz. Ele pega cada elemento de uma coleção e aplica uma função a ele, o que resulta em uma nova coleção em que cada elemento é a versão transformada do original.
Para visualizar isso, vejamos um exemplo:
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var doubledNumbers = numbers.Select(num => num * 2);
Aqui, usamos a expressão lambda que descreve como cada elemento num da lista numbers deve ser transformado.
O resultado é uma nova coleção doubledNumbers em que cada número é o dobro do valor original.
Da mesma forma, podemos usar a segunda sobrecarga:
var transformedNumbers = numbers.Select((num, index) => num * index);
Como podemos ver, assim como no exemplo anterior, usamos o método Select() para transformar uma coleção de inteiros, mas desta vez multiplicamos cada elemento da coleção pelo próprio índice.
Neste caso, o resultado também é uma nova coleção transformedNumbers.
Como funciona o método SelectMany
Agora que sabemos usar o método Select() no LINQ, vamos voltar a atenção para outra ferramenta poderosa do arsenal do LINQ: o método SelectMany().
Assim como no caso anterior, o LINQ nos oferece algumas sobrecargas para escolher:
public static IEnumerable<TResult> SelectMany<TSource,TCollection,TResult> (this IEnumerable<TSource> source, Func<TSource,IEnumerable<TCollection>> collectionSelector, Func<TSource,TCollection,TResult> resultSelector);
Esse método de extensão aceita a função collectionSelector, que transforma a coleção, e a função resultSelector, que transforma cada elemento da coleção achatada.
Como alternativa, podemos usar a outra sobrecarga:
public static IEnumerable<TResult> SelectMany<TSource,TCollection,TResult> (this IEnumerable<TSource> source, Func<TSource,int,IEnumerable<TCollection>> collectionSelector, Func<TSource,TCollection,TResult> resultSelector);
Assim como no caso do método Select(), também podemos usar uma sobrecarga que leva em conta o índice do elemento na coleção.
A seguir, temos a sobrecarga que podemos usar para achatar a coleção sem transformar os elementos dela:
public static IEnumerable<TResult> SelectMany<TSource,TResult> (this IEnumerable<TSource> source, Func<TSource,IEnumerable<TResult>> selector);
Desta vez, o método de extensão aceita apenas uma função selector, usada para achatar a coleção.
Por fim, temos uma variante dessa sobrecarga:
public static IEnumerable<TResult> SelectMany<TSource,TResult> (this IEnumerable<TSource> source, Func<TSource,int,IEnumerable<TResult>> selector);
Essa sobrecarga também aceita uma função selector como parâmetro, mas acrescenta um segundo parâmetro, que representa o índice do elemento.
Achatamento de coleções
O método SelectMany() foi projetado para os casos complexos em que lidamos com coleções de coleções.
Ele faz mais do que transformar cada elemento de uma coleção. Ele achata várias coleções aninhadas em uma única sequência unidimensional.
É esse achatamento que diferencia o método SelectMany() do método Select().

E-book gratuito
Sua Web API está pronta para produção?
33 itens para verificar antes de implantá-la, com a correção de cada um. Um PDF gratuito de 76 páginas para .NET 10.
O e-book está em inglês.
Baixe o checklist gratuitoPDF gratuito. Um único e-mail para enviá-lo. Cancele a inscrição quando quiser.
Os próximos exemplos usam duas classes e uma enumeração, que colocamos em um novo arquivo Models.cs ao lado de Program.cs:
public class Department
{
public string Name { get; set; }
public List<Employee> Employees { get; set; }
}
public class Employee
{
public string Name { get; set; }
public JobPosition JobPosition { get; set; }
}
public enum JobPosition
{
Admin,
Developer,
HumanResources
}
Para entender melhor o método SelectMany(), vamos definir uma lista:
var listOfDepartments = new List<Department>
{
new Department
{
Name = "TechSupport",
Employees = new()
{
new Employee { Name = "Thomas", JobPosition = JobPosition.Admin },
new Employee { Name = "Cynthia", JobPosition = JobPosition.Admin }
}
},
new Department
{
Name = "Development",
Employees = new()
{
new Employee { Name = "Eric", JobPosition = JobPosition.Developer },
new Employee { Name = "Laura", JobPosition = JobPosition.Developer },
new Employee { Name = "Cedric", JobPosition = JobPosition.Developer }
}
},
new Department
{
Name = "BackOffice",
Employees = new()
{
new Employee { Name = "Monica", JobPosition = JobPosition.HumanResources }
}
}
};
Aqui, temos uma lista de instâncias da classe Department. A classe Department contém uma propriedade Name, que representa o nome do departamento, e uma lista de instâncias de Employee em uma propriedade de coleção chamada Employees.
Por fim, a classe Employee contém as propriedades Name e JobPosition, que descrevem o funcionário.
Agora, vejamos como usar o método SelectMany() para achatá-la:
var listOfEmployees = listOfDepartments.SelectMany(department => department.Employees);
Aqui, usamos o método SelectMany() para extrair as listas Employee de cada elemento da lista listOfDepartments e combiná-las em uma única sequência. Depois dessa operação, a variável listOfEmployees guarda uma consulta que produz todos os nossos funcionários.
Como alternativa, podemos usar uma das sobrecargas para enriquecer os dados dos funcionários com os dados do departamento:
var detailedListOfEmployees = listOfDepartments.SelectMany(department => department.Employees,
(department, employee) => $"{employee.Name} | {department.Name} | {employee.JobPosition}")
.ToList();
Primeiro, achatamos a coleção de objetos Department em uma lista de objetos Employee.
Depois, modificamos cada elemento resultante dessa operação, transformando-o em uma string que contém dados dos objetos Department e Employee.
Um operador não é a única forma de obter uma sequência plana a partir de dados aninhados. O nosso guia sobre como achatar coleções aninhadas menciona o AutoMapper para os casos em que uma consulta não é o que queremos.
O que é o SelectMany em C# e o que ele faz?
A referência da Microsoft sobre Enumerable.SelectMany o define em uma linha: ele “[p]rojeta cada elemento de uma sequência em um IEnumerable<T> e achata as sequências resultantes em uma única sequência”. É o operador de achatamento do LINQ.
Dada uma lista de departamentos em que cada departamento tem uma lista de funcionários, Select() retorna uma sequência de listas de funcionários. SelectMany() retorna uma sequência de funcionários.
Ele achata exatamente um nível. Uma lista de listas vira uma lista. Uma lista de listas de listas vira uma lista de listas e precisa de uma segunda chamada.
A segunda forma útil dele é a sobrecarga com seletor de resultado, que mantém o elemento externo no escopo enquanto projeta o interno. É assim que obtemos “o funcionário mais o departamento de onde ele veio” em uma única passada, sem um join.
O compilador do C# também se apoia nele. Cada cláusula from extra em uma expressão de consulta vira uma chamada a SelectMany().
Os tipos contam a história toda:
var employeeLists = listOfDepartments.Select(d => d.Employees); // IEnumerable<List<Employee>> var employees = listOfDepartments.SelectMany(d => d.Employees); // IEnumerable<Employee>
Select vs SelectMany: qual é a diferença?
Agora que conhecemos os métodos Select() e SelectMany() do LINQ, é importante entender em que eles diferem e quando usar cada um. Embora possam parecer semelhantes à primeira vista, eles têm propósitos distintos e funcionam melhor em situações diferentes.
Os dois métodos fazem projeção. A diferença está no que a projeção retorna e no que o LINQ faz com isso depois.
Select() recebe um seletor que retorna um valor e produz um resultado por elemento de origem. O formato da origem se mantém: entram dez, saem dez.
SelectMany() recebe um seletor que retorna uma sequência e produz cada elemento de cada uma dessas sequências, em ordem. Com dez na entrada, obtemos tantos elementos quantos houver no total das sequências internas, o que pode ser mais, menos ou nenhum.
Errar isso tem um sintoma fácil de reconhecer. Projetar uma propriedade de coleção com Select() produz um IEnumerable<List<T>>, uma sequência de listas, e a próxima linha não compila, ou um foreach nos entrega listas em vez dos itens que esperávamos.
Os dois adiam a execução e os dois usam o padrão Iterator, então nenhum deles faz trabalho algum até que algo enumere o resultado.
Select() | SelectMany() |
|
|---|---|---|
| O seletor retorna | Um valor por elemento | Uma sequência por elemento |
| Quantidade de resultados | A mesma da origem | Soma dos tamanhos das sequências internas |
| Formato do resultado | IEnumerable<TResult> | IEnumerable<TResult>, achatado em um nível |
| Projeção de uma propriedade de coleção | IEnumerable<List<Employee>>, aninhado | IEnumerable<Employee>, plano |
| Níveis achatados | Nenhum | Exatamente um |
| Equivalente na sintaxe de consulta | Uma única cláusula select | Uma segunda cláusula from |
| Sobrecarga com o elemento de origem | Não se aplica | Seletor de resultado (source, inner) => ... |
| Sobrecarga com índice | Sim | Sim |
| Execução | Adiada | Adiada |
| Use quando | Cada item corresponde a um item | Cada item contém itens que queremos individualmente |
O compilador também recorre a SelectMany() por nós. Esta expressão de consulta produz a mesma sequência plana que a chamada de método:
employees = from department in listOfDepartments
from employee in department.Employees
select employee;
Não é uma coincidência de estilo. A referência do LINQ da Microsoft registra a regra: “Na sintaxe de expressão de consulta, cada cláusula from (C#) ou cláusula From (Visual Basic) depois da inicial é traduzida em uma invocação de SelectMany”.
Casos de uso
Para começar, vamos ver quando devemos usar o método Select() e quando usar o método SelectMany().
O método Select() pode ser usado para:
- Converter uma lista de um tipo em outro tipo
- Obter uma propriedade específica dos objetos de uma coleção
- Fazer cálculos, como no nosso exemplo, em que dobramos cada número
Por outro lado, o método SelectMany() é especialmente útil em situações em que precisamos:
- Achatar coleções ou arrays aninhados
- Fazer operações que envolvem combinar ou mesclar dados de várias coleções
- Lidar com estruturas de dados hierárquicas ou relacionais, em que os elementos estão aninhados dentro de outros elementos
Como podemos ver, cada um dos métodos tem um propósito próprio. E a projeção raramente vem sozinha: o método Where() restringe a origem antes de a projetarmos, e o nosso artigo sobre como selecionar vários registros com LINQ aplica a mesma filtragem a uma lista de chaves.
Estrutura da coleção resultante
Outro ponto importante é a diferença na estrutura das coleções resultantes.
O método Select() preserva, por padrão, a estrutura da coleção original. Se começarmos com uma coleção de 10 elementos, terminamos com outra coleção de 10 elementos, ainda que transformados.
Já o método SelectMany() altera a estrutura da coleção. Ele combina elementos de coleções aninhadas, o que muitas vezes resulta em uma coleção maior que a original, dependendo dos elementos aninhados.
Lógica interna do método Select
Agora, vejamos a forma interessante como o método Select() funciona por baixo dos panos:
Quando usamos o método Select() para projetar a coleção em uma nova forma, ele não é executado imediatamente.

E-book gratuito
Sua Web API está pronta para produção?
33 itens para verificar antes de implantá-la, com a correção de cada um. Um PDF gratuito de 76 páginas para .NET 10.
O e-book está em inglês.
Baixe o checklist gratuitoPDF gratuito. Um único e-mail para enviá-lo. Cancele a inscrição quando quiser.
O que ele faz é criar uma instância de IEnumerable que guarda todos os detalhes da operação que queremos executar. É isso que chamamos de execução adiada.
Quando começamos a percorrer esse IEnumerable, ele entra em ação com o padrão Iterator. Esse padrão permite processar cada elemento um por um, como faz um iterador com yield return. O nosso artigo sobre como o yield return viabiliza a execução adiada explica esse mecanismo em detalhes.
Cada elemento passa pela transformação definida pela expressão lambda que entregamos ao método Select(). Essa expressão diz o que deve ser feito com cada elemento.
Lógica interna do método SelectMany
Com tudo isso em mente, vejamos agora a lógica aplicada no método SelectMany():
Assim como o seu primo, o método Select(), o método SelectMany() adota a execução adiada e o padrão Iterator.
Quando o iterador entra em ação, ele processa cada elemento da coleção de origem usando uma expressão lambda que fornecemos.
Para cada item da coleção de origem, o método SelectMany() não se limita a iterar sobre o IEnumerable fornecido pela nossa lambda. Ele pega cada elemento dessas coleções aninhadas, um de cada vez, e o entrega para nós.
É isso que nos dá uma única sequência achatada.
A etapa Apply transformation do diagrama só é executada nas sobrecargas que recebem um seletor de resultado, como a que construiu detailedListOfEmployees. A sobrecarga que usamos para listOfEmployees chama a nossa lambda uma vez por departamento e retorna cada funcionário sem alteração.
Quando usar SelectMany em vez de Select?
Use SelectMany() quando cada elemento contém várias coisas que você quer tratar individualmente, e Select() quando cada elemento corresponde a exatamente uma coisa.
O teste prático é o tipo que o seletor retorna. Se a resposta for uma coleção, SelectMany() é o operador. Se for um único valor, é Select().
Recorra à sobrecarga com seletor de resultado quando o elemento achatado sozinho não bastar: quando a saída precisar dizer de qual pai veio cada item.
Duas chamadas são melhores que uma quando o aninhamento tem dois níveis, mas encadeá-las repetidamente é sinal de que quem está brigando conosco é o modelo, e não o operador.
Escolher entre os dois métodos não é uma decisão de desempenho. Os dois usam execução adiada e os dois processam em streaming, então o custo é o trabalho feito na lambda e a quantidade de elementos e de sequências internas produzidos. SelectMany() produz mais apenas porque pedimos mais.
Uso eficiente de Select e SelectMany
Tanto Select() quanto SelectMany() podem introduzir custo adicional, principalmente quando lidamos com coleções grandes.
Por isso, devemos minimizar o número de transformações e de operações de achatamento. Quando possível, devemos combinar várias transformações em uma única chamada a Select() ou SelectMany().
Precisamos lembrar que as consultas LINQ não são executadas até que iteremos sobre elas. Isso pode ser tanto uma vantagem para a eficiência quanto uma fonte de confusão, se não for bem gerenciado.
Portanto, devemos planejar quando os dados são consultados e transformados.
Embora a execução adiada possa melhorar o desempenho por não processar os dados até que seja necessário, ela também pode causar quedas de desempenho inesperadas se a mesma consulta for iterada várias vezes.
Por esse motivo, precisamos considerar armazenar os resultados em cache se quisermos reutilizá-los.
Considerações sobre desempenho
Outro ponto a considerar é o custo adicional de desempenho dos dois métodos.
O método Select() costuma ser eficiente, mas o desempenho pode cair se a função de transformação for complexa ou se ele for usado em coleções muito grandes.
A diferença de custo está principalmente na quantidade de elementos, e não no operador. Os dois usam execução adiada, e os dois executam um passo do iterador por elemento que produzem. SelectMany() produz mais elementos porque foi isso que pedimos a ele, e também abre um enumerador por sequência interna, o que fica visível quando as sequências internas são pequenas.
É nos dados profundamente aninhados que a quantidade de elementos aparece: mais elementos significam mais memória ocupada se materializarmos o resultado, e mais trabalho nas etapas seguintes.
Embora seja importante conhecer essas questões de desempenho, devemos evitar nos concentrar demais na otimização de desempenho desde o início, pois isso pode levar a um código complexo e difícil de ler.
Para lidar com isso, devemos sempre escrever primeiro um código claro e legível e, depois, otimizar com base em profiling e nas necessidades reais de desempenho.
Conclusão
Vimos aqui os dois operadores de projeção: o que cada um retorna, as sobrecargas que cada um oferece, a forma de expressão de consulta que o compilador reescreve como SelectMany() e a lógica de iteração por trás deles.
Enquanto o método Select é ideal para transformações simples dos elementos de uma coleção, o método SelectMany se destaca no achatamento de coleções aninhadas, o que o torna valioso para estruturas de dados mais complexas.
Ao entender os métodos Select e SelectMany e seguir as boas práticas de eficiência e desempenho, podemos escrever um código C# mais eficiente, legível e fácil de manter. O próprio LINQ também continua evoluindo, e o artigo sobre o que mudou no LINQ mostra os operadores que chegaram com o .NET 6.
Testado com .NET 10.0.10.

