Select and SelectMany in C#: What's the Difference and How Does It Affect Speed?
If you've ever written LINQ queries in C#, you've likely encountered two similar but very different methods: Select and SelectMany. At first glance, both "select something" from a collection. But the devil, as always, is in the details. Choosing the wrong one can turn your fast code into a sluggish mess, while the right choice can make it elegant and lightning-fast.
In this article, we won't just cover the theory. We'll conduct a real performance test, look at the internal workings of both methods, and give you a clear algorithm: when to use Select and when to use SelectMany. Ready? Let's go!
Quick Primer: What Are Select and SelectMany?
First, the basics. Imagine you have a list of students (List<Student>), and each student has a list of grades (List<int>).
Select is a "one-to-one" projection. For each element in the source collection, you get exactly one element as output. If you have 10 students, Select will return a collection of 10 elements.
- Example: Get the names of all students.
- Signature:
IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
SelectMany is a "one-to-many" projection followed by flattening. Each element in the source collection can produce multiple output elements, and they are all merged into a single flat list.
- Example: Get all grades of all students (a single flat list of numbers).
- Signature:
IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector)
The difference seems obvious. But where's the catch? The catch is in how these methods work under the hood and how beginners often confuse them.
The Main Difference: Output Data Shape
Let's solidify this with a code example. Here's our model class:
public class Student{ public string Name { get; set; } public List<int> Grades { get; set; } = new List<int>();}
// Creating datavar students = new List<Student>{ new Student { Name = "Anna", Grades = new List<int>{ 5, 4, 5 } }, new Student { Name = "Boris", Grades = new List<int>{ 3, 4 } }, new Student { Name = "Vika", Grades = new List<int>{ 5, 5, 5, 4 } }};Example 1: Using Select (incorrectly)
// We want to get all grades. But Select returns a collection of collections!var allGradesNested = students.Select(s => s.Grades);// Type: IEnumerable<List<int>> — three lists inside!
foreach (var gradeList in allGradesNested){ foreach (var grade in gradeList) { Console.Write(grade + " "); // We have to use a nested loop }}// Output: 5 4 5 3 4 5 5 5 4This works, but it's inconvenient. You get a nested structure that needs additional flattening.
Example 2: Using SelectMany (correctly)
// SelectMany does all the work for us: it takes the inner collections and flattens themvar allGradesFlat = students.SelectMany(s => s.Grades);// Type: IEnumerable<int> — a single flat list of numbers!
foreach (var grade in allGradesFlat){ Console.Write(grade + " "); // No nested loops!}// Output: 5 4 5 3 4 5 5 5 4Can you feel the differenc