CodeGym /Cours /C# SELF /Utilisation pratique des comparateurs dans .NET

Utilisation pratique des comparateurs dans .NET

C# SELF
Niveau 30 , Leçon 4
Disponible

1. Intro

Pourquoi les comparateurs, c’est vraiment utile…

Dans les vrais projets, c’est jamais aussi simple que dans les exos : les objets sont complexes, les données viennent des users ou d’autres services, les “ordres de tri” (ou même les règles d’égalité) changent tout le temps selon le contexte. Les comparateurs rendent ton code flexible, et le comportement — prévisible et sous contrôle.

Dans .NET, t’as besoin de comparateurs dès que tu veux trier, chercher, grouper ou virer les doublons d’objets de tes propres classes, ou quand tu construis des structures de données “ordonnées”. On les croise surtout dans les collections, les algos et quand tu bosses avec des API externes.

Où on croise les comparateurs dans .NET :

  • Tri des collections (List<T>.Sort, Array.Sort, OrderBy dans LINQ)
  • Structures de données ordonnées (SortedSet<T>, SortedDictionary<TKey, TValue>)
  • Recherche et comparaison d’objets dans les collections (Contains, IndexOf — quand tu dois définir “l’égalité”, pas juste l’ordre)
  • Groupement, filtrage et déduplication (genre, .Distinct())

Pourquoi c’est utile en vrai ?

  • Rapports où l’ordre compte (genre, liste d’étudiants par ordre alphabétique ou par moyenne)
  • Faire matcher des données entrantes avec des valeurs de référence (genre, chercher un enregistrement selon un critère)
  • Gagner en mémoire et en perf (bien choisir la structure de données accélère la recherche et rend l’app plus réactive)
  • Validation d’unicité (genre, à l’inscription d’un user par e-mail)

2. Exemple concret : tri selon différents critères

Imaginons qu’on a une classe utilisateur :

public class User
{
    public string FirstName { get; set; } = "";
    public string LastName { get; set; } = "";
    public string Email { get; set; } = "";
    public int Age { get; set; }

    // Tu peux override Equals et GetHashCode — mais ça, c’est pour la lecture à la maison
}

On a une liste d’utilisateurs, et on veut :

  • Les trier par nom de famille, et si c’est pareil — par prénom.
  • Pouvoir chercher un user par e-mail (sans tenir compte de la casse).
  • Éviter les doublons d’utilisateurs à l’ajout.

Tri avec IComparer<T>

Option 1 : la méthode classique — créer un comparateur à part.

// Comparateur pour trier par nom de famille puis prénom
public class UserFullNameComparer : IComparer<User>
{
    public int Compare(User? x, User? y)
    {
        if (ReferenceEquals(x, y)) return 0;
        if (x is null) return -1;
        if (y is null) return 1;

        int lastNameComparison = StringComparer.OrdinalIgnoreCase.Compare(x.LastName, y.LastName);
        if (lastNameComparison != 0)
            return lastNameComparison;

        return StringComparer.OrdinalIgnoreCase.Compare(x.FirstName, y.FirstName);
    }
}

Utilisation :

var users = new List<User>
{
    new User { FirstName = "Ivan", LastName = "Petrov", Age = 20, Email = "ivan.petrov@email.com" },
    new User { FirstName = "Anna", LastName = "Smirnova", Age = 22, Email = "anna.smirnova@email.com" },
    new User { FirstName = "Petr", LastName = "Petrov", Age = 18, Email = "petr.petrov@email.com" }
};

users.Sort(new UserFullNameComparer());

foreach (var user in users)
{
    Console.WriteLine($"{user.LastName} {user.FirstName} ({user.Age})");
}

// Résultat — les users sont triés par nom de famille, et si égalité, par prénom.

À retenir : On fait comme ça si l’ordre de tri doit servir à plusieurs endroits de l’app, et si une lambda ne suffit plus à éviter le copier-coller.

Tri “à la volée” avec une lambda

Pas envie de créer une classe juste pour un tri ponctuel ? Vive la lambda !

users.Sort((x, y) =>
{
    int lastNameComparison = StringComparer.OrdinalIgnoreCase.Compare(x.LastName, y.LastName);
    if (lastNameComparison != 0)
        return lastNameComparison;
    return StringComparer.OrdinalIgnoreCase.Compare(x.FirstName, y.FirstName);
});

Ça marche pareil, mais le comparateur est créé direct dans l’appel. Ça fait gagner des lignes, mais c’est pas toujours pratique si tu dois t’en servir plusieurs fois.

3. Recherche maligne : comparer les e-mails sans tenir compte de la casse

Dans la vraie vie, les users tapent leur e-mail n’importe comment, et toi — t’es dev, pas juge, donc c’est logique de comparer les e-mails sans se soucier de la casse.

On fait ça avec un comparateur et une recherche :

public class EmailComparer : IEqualityComparer<User>
{
    public bool Equals(User? x, User? y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x is null || y is null) return false;
        return string.Equals(x.Email, y.Email, StringComparison.OrdinalIgnoreCase);
    }
    public int GetHashCode(User obj)
    {
        return obj.Email?.ToLowerInvariant().GetHashCode() ?? 0;
    }
}

Utilisation dans HashSet :

var usersSet = new HashSet<User>(new EmailComparer());
usersSet.Add(new User { Email = "Petrov@example.com" });
bool contains = usersSet.Contains(new User { Email = "petrov@example.com" }); // true!

À noter : Quand tu fais ton EqualityComparer, oublie pas d’implémenter les deux méthodes : Equals et GetHashCode. Si t’oublies la deuxième — le comportement sera chelou : la recherche peut “bugguer” dans les collections.

4. Utilisation de SortedSet et SortedDictionary

C’est là que les comparateurs montrent vraiment leur puissance.

SortedSet<T> et SortedDictionary<TKey, TValue> ne peuvent pas bosser avec tes objets si tu n’expliques pas à .NET comment les comparer. Et l’ordre de comparaison et d’égalité influence quels éléments sont considérés comme différents !

Exemple avec SortedSet<User>

var sortedUsersByFullName = new SortedSet<User>(new UserFullNameComparer())
{
    new User { FirstName = "Ivan", LastName = "Petrov", Age = 20 },
    new User { FirstName = "Anna", LastName = "Smirnova", Age = 22 },
    new User { FirstName = "Petr", LastName = "Petrov", Age = 18 },
    new User { FirstName = "Ivan", LastName = "Petrov", Age = 25 } // Doublon sur nom et prénom
};

// "Ivan Petrov" avec des âges différents — un seul sera dans le set
Console.WriteLine("Utilisateurs dans SortedSet :");
foreach (var user in sortedUsersByFullName)
    Console.WriteLine($"{user.LastName} {user.FirstName} ({user.Age})");

SortedSet n’ajoutera pas deux “Ivan Petrov” !

Important : Ici, le comparateur définit la logique d’“unicité”. Si tu compares juste sur nom et prénom, alors les users avec le même nom/prénom mais un âge différent sont considérés comme le même user.

5. Tableau : quand utiliser quel comparateur dans .NET

Scénario Quoi implémenter Exemple d’utilisation
Ordre de tri naturel (un seul, universel pour le type)
IComparable<T>
List<T>.Sort()
Plusieurs façons de trier (par nom, âge, e-mail ...)
IComparer<T>
(classes, lambdas)
List<T>.Sort(comparer)
Recherche d’éléments uniques dans une collection
IEqualityComparer<T>
HashSet<T>
,
Dictionary<TKey, TValue>
Groupement, suppression de doublons
IEqualityComparer<T>
LINQ
.Distinct(comparer)
Tri par date, heure, ou combinaison complexe de champs
Comparison<T>
, lambda à la volée
List<T>.Sort((a, b) => ...)
Utilisation dans LINQ (requêtes ponctuelles) lambda dans
OrderBy
,
ThenBy
OrderBy(x => x.Name)

6. Pratique : recherche et comparaison dans les collections

On va coder dans notre app l’inscription d’un user avec un e-mail unique (casse ignorée). Si un user avec cette adresse existe déjà, faut le dire.

public static bool RegisterUser(List<User> users, User newUser)
{
    // On utilise Any avec une lambda pour vérifier l’unicité de l’e-mail
    bool exists = users.Any(u =>
        string.Equals(u.Email, newUser.Email, StringComparison.OrdinalIgnoreCase));
    if (exists)
    {
        Console.WriteLine($"Un utilisateur avec l’e-mail : {newUser.Email} est déjà inscrit !");
        return false;
    }
    users.Add(newUser);
    Console.WriteLine($"Utilisateur {newUser.FirstName} ajouté.");
    return true;
}

Utilisation :

var userList = new List<User>
{
    new User { Email = "first@example.com" }
};

RegisterUser(userList, new User { FirstName = "Vasya", Email = "FIRST@example.com" });
// Affichera : "Un utilisateur avec l’e-mail : FIRST@example.com est déjà inscrit !"

7. Erreurs classiques avec les comparateurs

Tout le monde aime les listes d’erreurs, mais on va faire ça à la cool.

Parfois, quand un dev fait un comparateur pour son type, il oublie de checker le null ou — pire — il code la comparaison de façon à casser la “rigueur de l’ordre”. Par exemple, si dans le comparateur tu retournes des valeurs contradictoires, le tri va devenir imprévisible, et dans les collections tu vas perdre ou “fusionner” des objets.

Autre souci fréquent — l’incohérence entre Equals et la logique du comparateur. Genre, si Equals dit que les objets sont différents, mais le comparateur dit qu’ils sont pareils, dans un SortedSet ou SortedDictionary c’est le bazar : l’élément n’est pas trouvé alors qu’il est là.

On voit aussi des cas où le dev compare juste un seul champ de l’objet (genre, le nom de famille), en oubliant qu’il peut y avoir plein d’autres users avec le même nom. Résultat : les objets “s’écrasent”, se perdent, et les données deviennent incohérentes. C’est-à-dire qu’elles ne reflètent plus la réalité du système — tu te retrouves avec des doublons, des infos qui disparaissent ou des bugs dans la logique de l’app.

1
Étude/Quiz
Comparateurs, niveau 30, leçon 4
Indisponible
Comparateurs
Comparateurs et comparaison d'objets
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION