1. Formas básicas de filtrar colecciones
Filtrar es como un colador para oro, solo que en vez de pepitas seleccionamos de la colección solo los elementos que nos interesan. Por ejemplo, tenemos una lista de usuarios — queremos encontrar solo los mayores de edad, o solo los estudiantes, o solo los que aman el café (vamos, programadores). Seleccionar los elementos adecuados es una tarea súper común, la encuentras en todas partes: desde trabajar con bases de datos hasta procesar la entrada del usuario.
Vamos a ver diferentes formas de filtrar en C#. A medida que avancemos, iremos mejorando nuestra app de consola educativa, añadiendo filtros.
Filtrado manual usando un bucle
Empezamos con la forma más simple y clásica: filtrar usando foreach:
// Ejemplo: tenemos una lista de números, hay que seleccionar solo los pares
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> evenNumbers = new List<int>();
foreach (int number in numbers)
{
if (number % 2 == 0) // si el número es par
{
evenNumbers.Add(number);
}
}
Console.WriteLine("Números pares:");
foreach (int n in evenNumbers)
{
Console.WriteLine(n);
}
Este método siempre funciona, es fácil de entender, pero no es el más compacto ni el más "moderno".
¿Por qué el filtrado manual no siempre mola?
Cuando tienes otra colección, otro criterio de filtrado, o necesitas escribir varios filtros a la vez — acabas con mucho código parecido. Apetece algo más compacto, legible y modular.
2. Filtrado por criterios complejos
Supón que tenemos una colección de objetos. Vamos a pillar el ejemplo de usuarios de nuestra app en desarrollo.
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsStudent { get; set; }
}
Declaramos una lista de usuarios:
List<User> users = new List<User>
{
new User { Name = "Anya", Age = 17, IsStudent = true },
new User { Name = "Boris", Age = 21, IsStudent = false },
new User { Name = "Vika", Age = 19, IsStudent = true },
new User { Name = "Gleb", Age = 25, IsStudent = false }
};
Ejemplo 1: Seleccionar todos los estudiantes
List<User> students = new List<User>();
foreach (User user in users)
{
if (user.IsStudent)
students.Add(user);
}
Console.WriteLine("Lista de estudiantes:");
foreach (User user in students)
{
Console.WriteLine($"{user.Name} ({user.Age})");
}
Ejemplo 2: Seleccionar estudiantes mayores de edad
List<User> adults = new List<User>();
foreach (User user in users)
{
if (user.Age >= 18 && user.IsStudent)
adults.Add(user);
}
Console.WriteLine("Estudiantes mayores de edad:");
foreach (User user in adults)
{
Console.WriteLine($"{user.Name} ({user.Age})");
}
¿Y si los criterios de filtrado son dinámicos?
Puedes sacar las condiciones de filtrado a métodos aparte, para llamarlos dentro del bucle:
bool IsAdult(User u) { return u.Age >= 18; }
bool IsStudent(User u) { return u.IsStudent; }
List<User> filtered = new List<User>();
foreach (User user in users)
{
if (IsAdult(user) && IsStudent(user))
filtered.Add(user);
}
3. Filtrado por clave en diccionarios
Listas y arrays están bien, pero muchas veces curramos también con diccionarios.
Supón que tenemos un diccionario donde la clave es el nombre del usuario y el valor es su edad:
Dictionary<string, int> ageByName = new Dictionary<string, int>
{
["Anya"] = 17,
["Boris"] = 21,
["Vika"] = 19,
["Gleb"] = 25
};
Tarea: seleccionar solo usuarios 18+
Dictionary<string, int> adults = new Dictionary<string, int>();
foreach (var pair in ageByName)
{
if (pair.Value >= 18)
adults.Add(pair.Key, pair.Value);
}
Console.WriteLine("Mayores de edad:");
foreach (var pair in adults)
{
Console.WriteLine($"{pair.Key}: {pair.Value} años");
}
Tarea: seleccionar usuarios con nombres que empiezan por "V"
Dictionary<string, int> namesWithV = new Dictionary<string, int>();
foreach (var pair in ageByName)
{
if (pair.Key.StartsWith("V"))
namesWithV.Add(pair.Key, pair.Value);
}
Console.WriteLine("Nombres que empiezan por 'V':");
foreach (var pair in namesWithV)
{
Console.WriteLine($"{pair.Key}: {pair.Value} años");
}
4. Principios del filtrado
Cómo funciona el proceso de filtrado
┌──────────────────────────────────┐
│ Lista: [1, 2, 3, 4, 5, 6] │
└──────────────────────────────────┘
│
▼
[Comprobación: n % 2 == 0]
│
▼
┌──────────────────────────────────┐
│ Resultado: [2, 4, 6] │
└──────────────────────────────────┘
Composición de filtros: filtrando por diferentes condiciones
Puedes hacer filtrados secuenciales si quieres seleccionar datos por etapas:
// Primero filtramos solo estudiantes
List<User> onlyStudents = new List<User>();
foreach (User user in users)
{
if (user.IsStudent)
onlyStudents.Add(user);
}
// Luego seleccionamos entre ellos solo los mayores de edad
List<User> adultStudents = new List<User>();
foreach (User user in onlyStudents)
{
if (user.Age >= 18)
adultStudents.Add(user);
}
O todo de golpe:
List<User> adultStudents = new List<User>();
foreach (User user in users)
{
if (user.IsStudent && user.Age >= 18)
adultStudents.Add(user);
}
Ordenación y otras operaciones
Puedes ordenar usando el método Sort para listas:
// Estudiantes mayores de edad, ordenados por edad
adultStudents.Sort((a, b) => a.Age.CompareTo(b.Age));
¡Hablaremos más sobre ordenación en la próxima lección!
5. Filtrado en escenarios de usuario
1. Filtrado de la entrada del usuario
Supón que nuestra app recibe una lista de notas y tiene que seleccionar todos los "cincos".
Console.Write("Introduce las notas separadas por espacio: ");
string input = Console.ReadLine();
List<int> grades = new List<int>();
foreach (string s in input.Split(' '))
{
if (int.TryParse(s, out int grade))
grades.Add(grade);
}
List<int> fives = new List<int>();
foreach (int grade in grades)
{
if (grade == 5)
fives.Add(grade);
}
Console.WriteLine("Cincos:");
foreach (var grade in fives)
{
Console.WriteLine(grade);
}
2. Filtrado por varias condiciones
Tarea: seleccionar usuarios-estudiantes de entre 18 y 22 años inclusive.
List<User> filtered = new List<User>();
foreach (User user in users)
{
if (user.IsStudent && user.Age >= 18 && user.Age <= 22)
filtered.Add(user);
}
3. Filtrado por presencia de elemento
Supón que tenemos una lista de strings y queremos seleccionar solo los que contienen la subcadena ".net" (sin distinguir mayúsculas/minúsculas).
List<string> technologies = new List<string> { "C#", ".NET", "Java", "dotnet", "JavaScript" };
List<string> netTechs = new List<string>();
foreach (var tech in technologies)
{
if (tech.ToLower().Contains(".net"))
netTechs.Add(tech);
}
foreach (var tech in netTechs)
{
Console.WriteLine(tech);
}
6. Particularidades y errores típicos al filtrar
Filtrar parece fácil, pero aquí puedes liarla. Vamos a ver algunos detalles.
Cambiar la colección original
Si filtras la colección original y luego cambias su contenido después del filtrado, — el resultado no cambia si ya creaste una lista nueva. Pero si solo guardaste la referencia a la lista vieja, los cambios en la colección original pueden afectar al resultado.
List<int> filtered = new List<int>();
foreach (int n in numbers)
{
if (n > 2)
filtered.Add(n);
}
// Hacemos un cambio en la lista original
numbers.Add(10);
foreach (var n in filtered)
{
Console.WriteLine(n); // 10 no aparecerá aquí
}
Resultado del filtrado manual
El resultado del filtrado manual suele ser una lista nueva y modificable (por ejemplo, List<T>), con la que puedes hacer lo que quieras — añadir, borrar elementos, etc.
Filtrado y rendimiento
Los filtros en bucles se ejecutan para cada elemento de la colección, así que si la función dentro de la condición es pesada — ojo. A veces es más fácil hacer un bucle foreach simple, sobre todo si tienes lógica compleja y necesitas loguear o hacer acciones extra.
GO TO FULL VERSION