1. Les bases du filtrage de collections
Le filtrage, c'est comme un tamis à or, sauf qu'au lieu de pépites, on garde juste les éléments qu'on veut dans la collection. Par exemple, t'as une liste d'utilisateurs — tu veux trouver que les majeurs, ou que les étudiants, ou que ceux qui kiffent le café (bref, les devs). Sélectionner les bons éléments, c'est un truc qu'on fait tout le temps, que ce soit avec une DB ou en traitant l'entrée utilisateur.
On va voir différentes façons de filtrer en C#. Au fur et à mesure, on va faire évoluer notre appli console d'apprentissage en y ajoutant des filtres.
Filtrage manuel avec une boucle
On commence par la méthode la plus simple et classique : filtrer avec foreach :
// Exemple : on a une liste de nombres, faut garder que les pairs
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 le nombre est pair
{
evenNumbers.Add(number);
}
}
Console.WriteLine("Nombres pairs :");
foreach (int n in evenNumbers)
{
Console.WriteLine(n);
}
Cette méthode marche tout le temps, elle est facile à piger, mais c'est pas la plus compacte ni la plus "moderne".
Pourquoi le filtrage manuel, c'est pas toujours pratique ?
Quand t'as une autre collection, un autre critère de filtrage, ou que tu dois écrire plusieurs filtres d'un coup — tu te retrouves avec plein de code qui se ressemble. On veut plus de compacité, de lisibilité et de modularité.
2. Filtrage avec des critères complexes
Imaginons qu'on a une collection d'objets. On prend l'exemple des utilisateurs de notre appli en cours de dev.
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsStudent { get; set; }
}
On déclare une liste d'utilisateurs :
List<User> users = new List<User>
{
new User { Name = "Ania", 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 }
};
Exemple 1 : Garder que les étudiants
List<User> students = new List<User>();
foreach (User user in users)
{
if (user.IsStudent)
students.Add(user);
}
Console.WriteLine("Liste des étudiants :");
foreach (User user in students)
{
Console.WriteLine($"{user.Name} ({user.Age})");
}
Exemple 2 : Garder les étudiants majeurs
List<User> adults = new List<User>();
foreach (User user in users)
{
if (user.Age >= 18 && user.IsStudent)
adults.Add(user);
}
Console.WriteLine("Étudiants majeurs :");
foreach (User user in adults)
{
Console.WriteLine($"{user.Name} ({user.Age})");
}
Et si les critères de filtrage changent ?
Tu peux sortir les conditions de filtrage dans des méthodes à part, pour les appeler dans la boucle :
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. Filtrage par clé dans les dictionnaires
Les listes et tableaux, c'est cool, mais souvent on bosse aussi avec des dictionnaires.
Imaginons qu'on a un dico où la clé c'est le nom de l'utilisateur, et la valeur c'est son âge :
Dictionary<string, int> ageByName = new Dictionary<string, int>
{
["Ania"] = 17,
["Boris"] = 21,
["Vika"] = 19,
["Gleb"] = 25
};
Objectif : garder que les utilisateurs 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("Majeurs :");
foreach (var pair in adults)
{
Console.WriteLine($"{pair.Key}: {pair.Value} ans");
}
Objectif : garder les utilisateurs dont le nom commence par "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("Noms qui commencent par 'V' :");
foreach (var pair in namesWithV)
{
Console.WriteLine($"{pair.Key}: {pair.Value} ans");
}
4. Principes du filtrage
Comment marche le process de filtrage
┌──────────────────────────────────┐
│ Liste : [1, 2, 3, 4, 5, 6] │
└──────────────────────────────────┘
│
▼
[Test : n % 2 == 0]
│
▼
┌──────────────────────────────────┐
│ Résultat : [2, 4, 6] │
└──────────────────────────────────┘
Composer les filtres : filtrer sur plusieurs conditions
Tu peux faire des filtrages successifs, si tu veux sélectionner les données étape par étape :
// D'abord on garde que les étudiants
List<User> onlyStudents = new List<User>();
foreach (User user in users)
{
if (user.IsStudent)
onlyStudents.Add(user);
}
// Ensuite, parmi eux, on garde que les majeurs
List<User> adultStudents = new List<User>();
foreach (User user in onlyStudents)
{
if (user.Age >= 18)
adultStudents.Add(user);
}
Ou tout d'un coup :
List<User> adultStudents = new List<User>();
foreach (User user in users)
{
if (user.IsStudent && user.Age >= 18)
adultStudents.Add(user);
}
Tri et autres opérations
Pour trier, tu peux utiliser la méthode Sort sur les listes :
// Étudiants majeurs triés par âge
adultStudents.Sort((a, b) => a.Age.CompareTo(b.Age));
On parlera du tri plus en détail à la prochaine conf !
5. Filtrage dans les scénarios utilisateurs
1. Filtrer l'entrée utilisateur
Imaginons que notre appli prend une liste de notes et doit garder que les "5".
Console.Write("Entre les notes séparées par un espace : ");
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("Les 5 :");
foreach (var grade in fives)
{
Console.WriteLine(grade);
}
2. Filtrage sur plusieurs conditions
Objectif : garder les utilisateurs étudiants entre 18 et 22 ans inclus.
List<User> filtered = new List<User>();
foreach (User user in users)
{
if (user.IsStudent && user.Age >= 18 && user.Age <= 22)
filtered.Add(user);
}
3. Filtrage par présence d'un élément
On a une liste de strings, et on veut garder que celles qui contiennent la sous-chaîne ".net" (peu importe la casse).
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. Particularités et pièges classiques du filtrage
Le filtrage a l'air simple, mais tu peux vite faire des boulettes. On va voir quelques points à surveiller.
Modifier la collection d'origine
Si tu filtres une collection, puis tu modifies son contenu après le filtrage, — le résultat ne changera pas si t'as déjà créé une nouvelle liste. Mais si t'as juste gardé la référence à l'ancienne liste, les modifs sur la collection d'origine peuvent impacter le résultat.
List<int> filtered = new List<int>();
foreach (int n in numbers)
{
if (n > 2)
filtered.Add(n);
}
// On modifie la liste d'origine
numbers.Add(10);
foreach (var n in filtered)
{
Console.WriteLine(n); // 10 n'apparaîtra pas ici
}
Résultat du filtrage manuel
Le résultat d'un filtrage manuel, c'est généralement une nouvelle liste modifiable (genre List<T>), sur laquelle tu peux tout faire — ajouter, supprimer des éléments, etc.
Filtrage et perf
Les filtres dans les boucles sont appelés pour chaque élément de la collection, donc si la fonction dans la condition est lourde — fais gaffe. Parfois, c'est plus simple de faire une boucle foreach classique, surtout si t'as de la logique complexe, du logging ou d'autres actions à faire.
GO TO FULL VERSION