Select y SelectMany en C#: ¿cuál elegir para velocidad?

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Select y SelectMany en C#: ¿cuál es la diferencia y cómo afecta la velocidad?



Si alguna vez has escrito consultas LINQ en C#, seguramente te has encontrado con dos métodos similares pero muy diferentes: Select y SelectMany. A primera vista, ambos "seleccionan algo" de una colección. Pero el diablo, como siempre, está en los detalles. Elegir incorrectamente entre ellos puede convertir tu código rápido en un desastre lento, mientras que la elección correcta lo hará elegante y ultrarrápido.



En este artículo no solo analizaremos la teoría. Realizaremos una prueba de rendimiento real, veremos el funcionamiento interno de ambos métodos y te daremos un algoritmo claro: cuándo usar Select y cuándo usar SelectMany. ¿Listo? ¡Vamos allá!



Breve introducción: ¿qué son Select y SelectMany?



Para empezar, lo básico. Imagina que tienes una lista de estudiantes (List<Student>), y cada estudiante tiene una lista de calificaciones (List<int>).



Select es una proyección "uno a uno". Para cada elemento de la colección original, obtienes exactamente un elemento de salida. Si tienes 10 estudiantes, Select devolverá una colección de 10 elementos.



  • Ejemplo: Obtener los nombres de todos los estudiantes.
  • Firma: IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)


SelectMany es una proyección "uno a muchos" seguida de un "aplanamiento" (flatten). Cada elemento de la colección original puede generar varios elementos de salida, y todos se combinan en una sola lista plana.



  • Ejemplo: Obtener todas las calificaciones de todos los estudiantes (una sola lista plana de números).
  • Firma: IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector)


Parece que la diferencia es obvia. Pero, ¿dónde está el truco? El truco está en cómo funcionan estos métodos internamente y cómo los principiantes suelen confundirlos.



La principal diferencia: la forma de los datos de salida



Vamos a fijarlo de inmediato con un ejemplo de código. Aquí está nuestra clase modelo:



public class Student{    public string Name { get; set; }    public List<int> Grades { get; set; } = new List<int>();}

// Creamos los datosvar students = new List<Student>{ new Student { Name = "Ana", 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 } }};


Ejemplo 1: Usando Select (incorrecto)



// Queremos obtener todas las calificaciones. Pero Select devolverá una colección de colecciones.var allGradesNested = students.Select(s => s.Grades);// Tipo: IEnumerable<List<int>> — ¡tres listas adentro!

foreach (var gradeList in allGradesNested){ foreach (var grade in gradeList) { Console.Write(grade + " "); // Tenemos que usar un bucle anidado }}// Salida: 5 4 5 3 4 5 5 5 4


Esto funciona, pero es incómodo. Obtienes una estructura anidada que debes aplanar adicionalmente.



Ejemplo 2: Usando SelectMany (correcto)



// SelectMany hace todo el trabajo por nosotros: toma las colecciones internas y las "aplana"var allGradesFlat = students.SelectMany(s => s.Grades);// Tipo: IEnumerable<int> — ¡una sola lista plana de números!

foreach (var grade in allGradesFlat){ Console.Write(grade + " "); // ¡Sin bucles anidados!}// Salida: 5 4 5 3 4 5 5 5 4


¿Sientes la diferencia?

Blogs

Book Recommendations