Plusieurs fonctions Linq.Enumerable prennent un IEqualityComparer<T>
. Existe-t-il une classe wrapper pratique qui adapte un delegate(T,T)=>bool
à implémenter IEqualityComparer<T>
? Il est assez facile d'en écrire un (si vous ignorez les problèmes de définition d'un hashcode correct), mais j'aimerais savoir s'il existe une solution prête à l'emploi.
Plus précisément, je souhaite effectuer des opérations de définition sur Dictionary
s, en utilisant uniquement les clés pour définir l'appartenance (tout en conservant les valeurs selon différentes règles).
IEqualityComparer<T>
qui laisse deGetHashCode
côté est tout simplement cassé.Sur l'importance de
GetHashCode
D'autres ont déjà commenté le fait que toute
IEqualityComparer<T>
implémentation personnalisée devrait vraiment inclure uneGetHashCode
méthode ; mais personne n'a pris la peine d'expliquer pourquoi en détail.Voici pourquoi. Votre question mentionne spécifiquement les méthodes d'extension LINQ; presque tous reposent sur des codes de hachage pour fonctionner correctement, car ils utilisent des tables de hachage en interne pour plus d'efficacité.
Prenez
Distinct
, par exemple. Considérez les implications de cette méthode d'extension si tout ce qu'elle utilisait était uneEquals
méthode. Comment déterminer si un élément a déjà été numérisé dans une séquence si vous ne l'avez déjà faitEquals
? Vous énumérez l'ensemble de la collection de valeurs que vous avez déjà consultées et recherchez une correspondance. Cela entraînerait l'Distinct
utilisation d'un algorithme O (N 2 ) du pire des cas au lieu d'un algorithme O (N)!Heureusement, ce n'est pas le cas.
Distinct
ne pas simplement utiliserEquals
; il utiliseGetHashCode
aussi. En fait, il ne fonctionne absolument pas correctement sans unIEqualityComparer<T>
qui fournit un bonGetHashCode
. Voici un exemple artificiel illustrant cela.Disons que j'ai le type suivant:
Maintenant, disons que j'ai un
List<Value>
et je veux trouver tous les éléments avec un nom distinct. C'est un cas d'utilisation parfait pourDistinct
utiliser un comparateur d'égalité personnalisé. Alors utilisons laComparer<T>
classe de la réponse d' Aku :Maintenant, si nous avons un tas d'
Value
éléments avec la mêmeName
propriété, ils devraient tous se réduire en une seule valeur renvoyée parDistinct
, non? Voyons voir...Production:
Hmm, ça n'a pas marché, n'est-ce pas?
Et quoi
GroupBy
? Essayons ça:Production:
Encore une fois: n'a pas fonctionné.
Si vous y réfléchissez, il serait logique
Distinct
d'utiliser aHashSet<T>
(ou équivalent) en interne, etGroupBy
d'utiliser quelque chose comme un enDictionary<TKey, List<T>>
interne. Cela pourrait-il expliquer pourquoi ces méthodes ne fonctionnent pas? Essayons ça:Production:
Ouais ... commence à avoir un sens?
Espérons qu'à partir de ces exemples, il est clair pourquoi l'inclusion d'un approprié
GetHashCode
dans touteIEqualityComparer<T>
implémentation est si importante.Réponse originale
Élargissement de la réponse d'orip :
Il y a quelques améliorations qui peuvent être apportées ici.
Func<T, TKey>
au lieu deFunc<T, object>
; cela empêchera l'encadrement des clés de type valeur dans le réelkeyExtractor
lui-même.where TKey : IEquatable<TKey>
contrainte; cela évitera le boxing dans l'Equals
appel (object.Equals
prend unobject
paramètre; vous avez besoin d'uneIEquatable<TKey>
implémentation pour prendre unTKey
paramètre sans le boxing). Clairement, cela peut poser une restriction trop sévère, vous pouvez donc créer une classe de base sans la contrainte et une classe dérivée avec elle.Voici à quoi pourrait ressembler le code résultant:
la source
StrictKeyEqualityComparer.Equals
méthode semble être la même queKeyEqualityComparer.Equals
. LaTKey : IEquatable<TKey>
contrainte fait-elleTKey.Equals
fonctionner différemment?TKey
peut s'agir de n'importe quel type arbitraire, le compilateur utilisera la méthode virtuelleObject.Equals
qui nécessitera la mise en boîte des paramètres de type valeur, par exempleint
. Dans ce dernier cas, cependant,TKey
étant contraint à mettre en œuvreIEquatable<TKey>
, laTKey.Equals
méthode sera utilisée qui ne nécessitera aucune boxe.StringKeyEqualityComparer<T, TKey>
trop.Lorsque vous souhaitez personnaliser la vérification d'égalité, 99% du temps, vous êtes intéressé par la définition des clés à comparer, pas par la comparaison elle-même.
Cela pourrait être une solution élégante (concept de la méthode de tri de liste de Python ).
Usage:
La
KeyEqualityComparer
classe:la source
Func<T, int>
pour fournir le code de hachage d'uneT
valeur (comme cela a été suggéré par exemple dans la réponse de Ruben ). Sinon, l'IEqualityComparer<T>
implémentation qui vous reste est assez cassée, en particulier en ce qui concerne son utilité dans les méthodes d'extension LINQ. Voir ma réponse pour une discussion sur pourquoi c'est.J'ai bien peur qu'il n'y ait pas de telle enveloppe prête à l'emploi. Cependant, il n'est pas difficile d'en créer un:
la source
private readonly Func<T, int> _hashCodeResolver
qui doit également être passé dans le constructeur et utilisé dans laGetHashCode(...)
méthode.obj.ToString().ToLower().GetHashCode()
au lieu deobj.GetHashCode()
?IEqualityComparer<T>
hachage invariablement utilisent le hachage dans les coulisses (par exemple, GroupBy, Distinct, Except, Join de LINQ, etc.) et le contrat MS concernant le hachage est rompu dans cette implémentation. Voici l'extrait de la documentation de MS: "Des implémentations sont nécessaires pour garantir que si la méthode Equals renvoie true pour deux objets x et y, la valeur renvoyée par la méthode GetHashCode pour x doit être égale à la valeur renvoyée pour y." Voir: msdn.microsoft.com/en-us/library/ms132155Identique à la réponse de Dan Tao, mais avec quelques améliorations:
S'appuie sur
EqualityComparer<>.Default
pour faire la comparaison réelle afin d'éviter la boxe pour les types de valeurstruct
qui ont été implémentésIEquatable<>
.Depuis
EqualityComparer<>.Default
utilisé, il n'explose pasnull.Equals(something)
.Fournit un wrapper statique autour
IEqualityComparer<>
duquel aura une méthode statique pour créer l'instance de comparateur - facilite l'appel. Compareravec
Ajout d'une surcharge à spécifier
IEqualityComparer<>
pour la clé.La classe:
vous pouvez l'utiliser comme ceci:
La personne est une classe simple:
la source
Avec extensions: -
la source
La réponse d'orip est excellente.
Voici une petite méthode d'extension pour le rendre encore plus facile:
la source
Je vais répondre à ma propre question. Pour traiter les dictionnaires comme des ensembles, la méthode la plus simple semble être d'appliquer des opérations d'ensembles à dict.Keys, puis de les reconvertir en dictionnaires avec Enumerable.ToDictionary (...).
la source
L'implémentation à (texte allemand) Implémentation de IEqualityCompare avec l'expression lambda se soucie des valeurs nulles et utilise des méthodes d'extension pour générer IEqualityComparer.
Pour créer un IEqualityComparer dans une union Linq, il vous suffit d'écrire
Le comparateur:
Vous devez également ajouter une méthode d'extension pour prendre en charge l'inférence de type
la source
Juste une optimisation: nous pouvons utiliser EqualityComparer prêt à l'emploi pour les comparaisons de valeurs, plutôt que de les déléguer.
Cela rendrait également l'implémentation plus propre car la logique de comparaison réelle reste maintenant dans GetHashCode () et Equals () que vous avez peut-être déjà surchargés.
Voici le code:
N'oubliez pas de surcharger les méthodes GetHashCode () et Equals () sur votre objet.
Cet article m'a aidé: c # comparer deux valeurs génériques
Sushil
la source
La réponse d'orip est excellente. Élargissement de la réponse d'orip:
Je pense que la clé de la solution est d'utiliser la "méthode d'extension" pour transférer le "type anonyme".
Usage:
la source
Cela permet de sélectionner une propriété avec lambda comme ceci:
.Select(y => y.Article).Distinct(x => x.ArticleID);
la source
Je ne connais pas de classe existante mais quelque chose comme:
Remarque: je n'ai pas encore compilé et exécuté ceci, il peut donc y avoir une faute de frappe ou un autre bogue.
la source