Select e SelectMany em C#: qual a diferença e como isso afeta a velocidade?
Se você já escreveu consultas LINQ em C#, certamente já se deparou com dois métodos semelhantes, mas muito diferentes: Select e SelectMany. À primeira vista, ambos "selecionam algo" de uma coleção. Mas o diabo, como sempre, está nos detalhes. A escolha errada entre eles pode transformar seu código rápido em uma bagunça lenta, enquanto a escolha certa pode torná-lo elegante e extremamente rápido.
Neste artigo, não vamos apenas analisar a teoria. Vamos realizar um teste de desempenho real, observar o funcionamento interno de ambos os métodos e dar a você um algoritmo claro: quando usar Select e quando usar SelectMany. Pronto? Vamos lá!
Breve introdução: o que são Select e SelectMany?
Para começar, o básico. Imagine que você tem uma lista de alunos (List<Student>), e cada aluno tem uma lista de notas (List<int>).
Select é uma projeção "um para um". Para cada elemento da coleção original, você obtém exatamente um elemento na saída. Se você tem 10 alunos, Select retornará uma coleção de 10 elementos.
- Exemplo: Obter os nomes de todos os alunos.
- Assinatura:
IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
SelectMany é uma projeção "um para muitos" seguida de "achatamento" (flatten). Cada elemento da coleção original pode gerar vários elementos na saída, e todos eles são combinados em uma única lista plana.
- Exemplo: Obter todas as notas de todos os alunos (uma única lista plana de números).
- Assinatura:
IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector)
Parece que a diferença é óbvia. Mas onde está o problema? O problema está em como esses métodos funcionam internamente e como os iniciantes frequentemente os confundem.
Principal diferença: formato dos dados de saída
Vamos fixar isso com um exemplo de código. Aqui está nossa classe modelo:
public class Student{ public string Name { get; set; } public List<int> Grades { get; set; } = new List<int>();}
// Criando os dadosvar students = new List<Student>{ new Student { Name = "Ana", Grades = new List<int>{ 5, 4, 5 } }, new Student { Name = "Bruno", Grades = new List<int>{ 3, 4 } }, new Student { Name = "Carla", Grades = new List<int>{ 5, 5, 5, 4 } }};Exemplo 1: Usando Select (incorreto)
// Queremos obter todas as notas. Mas Select retornará uma coleção de coleções!var allGradesNested = students.Select(s => s.Grades);// Tipo: IEnumerable<List<int>> — três listas internas!
foreach (var gradeList in allGradesNested){ foreach (var grade in gradeList) { Console.Write(grade + " "); // É necessário usar um loop aninhado }}// Saída: 5 4 5 3 4 5 5 5 4Isso funciona, mas é inconveniente. Você obtém uma estrutura aninhada que precisa ser desdobrada adicionalmente.
Exemplo 2: Usando SelectMany (correto)
// SelectMany faz todo o trabalho para nós: pega as coleções internas e as "achata"var allGradesFlat = students.SelectMany(s => s.Grades);// Tipo: IEnumerable<int> — uma única lista plana de números!
foreach (var grade in allGradesFlat){ Console.Write(grade + " "); // Sem loops aninhados!}// Saída: 5 4 5 3 4 5 5 5 4Sente a diferença