Comment doit-on s’y prendre pour recouper, en éléments distincts, une liste composée d’objets complexes? À un moment donné ou à un autre, nous avons tous eu à faire cela. Heureusement, c’est là qu’intervient la méthode Enumerable.Distinct().
Par définition, la méthode Distinct() permet de retourner une liste composée des éléments distincts de la collection d’origine.
Naturellement, il est simple d’utiliser Distinct avec une liste composée de types primitifs comme int, string, long et double, par exemple. La comparaison est déjà prévue dans le framework .NET.
Cependant, la situation est entièrement différente lorsqu’il est temps d’utiliser un type complexe avec Distinct. Comment doit s’y prendre pour deviner la clé pour comparer les différents éléments de la liste?
L’option la plus évidente est d’implémenter l’interface IEqualityComparer. Ce n’est pas par hasard que je le mentionne, car c’est justement une des signatures permises par Distinct. Vous pouvez créer une classe pour implémenter une comparaison, selon vos besoins, de votre type complexe.
Cela signifie que vous devez spécifier votre propre définition qu’égalité entre deux instances de ce type. Pour .NET, la définition d’égalité se définit par les méthodes suivantes:
- la valeur retournée par GetHashCode() doit être égale pour les deux objets comparés
- En cas de valeur différente au point précédent, une comparaison sera effectuée avec la méthode Equals() des deux instances.
Distinct et IEqualityComparer en exemple
Considérez les deux extraits de code suivant
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class User | |
{ | |
public string FirstName { get; set; } | |
public string LastName { get; set; } | |
public LoginInformation LoginInformation { get; set; } | |
public class Comparer : IEqualityComparer<User> | |
{ | |
public bool Equals(User x, User y) | |
{ | |
return x.LoginInformation.LoginLocation == y.LoginInformation.LoginLocation; | |
} | |
public int GetHashCode(User obj) | |
{ | |
return obj.LoginInformation.LoginLocation.GetHashCode(); | |
} | |
} | |
} | |
public class LoginInformation | |
{ | |
public string LoginLocation { get; set; } | |
} |
et l’application console suivante
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var users = new List<User> | |
{ | |
new User | |
{ | |
FirstName = "Pascal", | |
LastName = "Paradis", | |
LoginInformation = new LoginInformation | |
{ | |
LoginLocation = "Montréal" | |
} | |
}, | |
new User | |
{ | |
FirstName = "L'homme", | |
LastName = "Poux", | |
LoginInformation = new LoginInformation | |
{ | |
LoginLocation = "Montréal" | |
} | |
} | |
}; | |
Console.WriteLine(users.Distinct(new User.Comparer()).Single().LoginInformation.LoginLocation); | |
Console.ReadLine(); | |
} | |
} |
Êtes-vous en mesure de prédire le résultat de l’exécution? Dans la console, la chaîne de texte « Montréal » sera retournée à l’écran, car il s’agit de la valeur qui est comparée par la classe User.Comparer.
Certains pourraient argumenter que de faire un override des méthodes GetHashCode et Equals directement revient exactement au même. Là où je ne suis pas en accord c’est sur le principe que d’implémenter IEqualityComparer a le mérite d’exprimer clairement une intention.
Dans ce cas-ci, c’est de définir que deux utilisateurs sont recoupés en fonction de leur emplacement de connexion. L’élégance de la chose est qu’il n’y a rien qui vous empêche de développer plusieurs comparateurs implémentant IEqualityComparer selon le contexte où vous vous trouvez.
J’aime bien aussi l’idée de l’interface IEqualityComparer pour que ça soit explicite… Par contre me suis simplifié la vie pour ne pas avoir à écrire un comparateur par type en m’inspirant de ça:
http://www.blackwasp.co.uk/LambdaEqualityComparer.aspx
Super intéressant! Je vais y jeter un coup d’oeil. Merci!