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 hidden or 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
This file contains hidden or 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
Ê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.
Quel est le meilleur moyen pour indiquer qu’il faut cesser d’utiliser une classe, une méthode ou une propriété énormément sollicitée de votre code? L’une des stratégies les plus appropriées est de passer par l’utilisation de l’attribue [Obsolete] qui est inclus à même le framework .NET.
L’utilisation de cet attribut permet d’indiquer aux consommateurs de votre librairie que certaines de vos fonctions sont en cours de dépréciation au profit d’un autre ensemble de fonctionnalités. Ce qui rend intéressant cet approche, c’est que le compilateur C# ainsi que Visual Studio sont aussi liés à cet attribut. Lorsque l’attribut est utilisé, des avertissements de compilation seront affichés aux différents endroits où les éléments de code dépréciés seront utilisés.
C’est très gentil comme approche, lorsqu’on y pense.
Mise en contexte
L’attribut [Obsolete] s’utilise de plusieurs façons. Considérons la classe UserDirectory qui suit.
This file contains hidden or 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
Pour bonifier l’exemple, disons que cette classe est consommée dans un namespace différent dans une classe UserSearchEngine.
This file contains hidden or 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
Supposons que nous ajoutions [Obsolete] sur la classe UserDirectory. Dorénavant, deux effets se produiraient visuellement au nivea de Visual Studio.
Un indicateur visuel ainsi qu’un avertissement de compilation est maintenant affiché dans l’IDE. Il n’est vraiment pas possible de passer à côté.
Scénario #2 – [Obsolete(<string>)]
Il est possible d’ajouter un paramètre de type string à l’attribut Obsolete afin d’ajouter un message qui sera ajouté à l’avertissement de compilation. Il suffit d’écrire l’attribut ainsi: [Obsolete(« Utilisez UserRepository en remplacement »)].
Cela aura effet de produire un avertissement de compilation qui prend la forme suivante:
Scénario #3 – [Obsolete(<string>, <bool>)]
Cette signature de l’attribue Obsolete est, à mon avis, l’équivalent d’une bombe nucléaire. Le paramètre bool qui est ajouté permet d’indiquer au compilateur de générer une erreur de compilation au lieu d’un avertissement.
Lors qu’activé, ce paramètre fait en sorte que votre projet ne compilera pas tant et aussi longtemps que vous n’aurez pas réglé la ou les fonctionnalités dépréciées. Au moins, la signature fait en sorte que vous devez obligatoirement indiquer un message lorsque vous activez la génération d’erreurs avec [Obsolete]. Cela fait un peu moins rude pour vos consommateurs!