Mehrere Linq.Enumerable-Funktionen benötigen eine IEqualityComparer<T>
. Gibt es eine praktische Wrapper-Klasse, die a delegate(T,T)=>bool
an die Implementierung anpasst IEqualityComparer<T>
? Es ist einfach genug, einen zu schreiben (wenn Sie Probleme mit der Definition eines korrekten Hashcodes ignorieren), aber ich würde gerne wissen, ob es eine sofort einsatzbereite Lösung gibt.
Insbesondere möchte ich Set-Operationen für Dictionary
s ausführen und nur die Schlüssel zum Definieren der Mitgliedschaft verwenden (wobei die Werte nach verschiedenen Regeln beibehalten werden).
IEqualityComparer<T>
, was weggelassen wirdGetHashCode
, ist einfach kaputt.Über die Bedeutung von
GetHashCode
Andere haben bereits kommentiert, dass jede benutzerdefinierte
IEqualityComparer<T>
Implementierung wirklich eineGetHashCode
Methode enthalten sollte . aber niemand hat sich die Mühe gemacht, im Detail zu erklären, warum .Hier ist der Grund. In Ihrer Frage werden speziell die LINQ-Erweiterungsmethoden erwähnt. Fast alle von ihnen sind auf Hash-Codes angewiesen, um ordnungsgemäß zu funktionieren, da sie aus Effizienzgründen intern Hash-Tabellen verwenden.
Nehmen wir
Distinct
zum Beispiel. Berücksichtigen Sie die Auswirkungen dieser Erweiterungsmethode, wenn nur eineEquals
Methode verwendet wurde. Wie stellen Sie fest, ob ein Artikel bereits in einer Sequenz gescannt wurde, wenn Sie nur habenEquals
? Sie zählen die gesamte Sammlung von Werten auf, die Sie bereits angesehen haben, und suchen nach Übereinstimmungen. Dies würde dazu führen, dass anstelle eines O (N) -Algorithmus ein O (N 2 ) -Algorithmus imDistinct
ungünstigsten Fall verwendet wird !Zum Glück ist dies nicht der Fall.
Distinct
nicht nur verwendenEquals
; es verwendetGetHashCode
auch. In der Tat funktioniert es absolut nicht richtig ohne eineIEqualityComparer<T>
, die eine ordnungsgemäße liefertGetHashCode
. Unten sehen Sie ein Beispiel, das dies veranschaulicht.Angenommen, ich habe den folgenden Typ:
Sagen Sie jetzt, ich habe eine
List<Value>
und ich möchte alle Elemente mit einem eindeutigen Namen finden. Dies ist ein perfekter Anwendungsfall für dieDistinct
Verwendung eines benutzerdefinierten Gleichheitsvergleichs. Verwenden wir also dieComparer<T>
Klasse aus Akus Antwort :Wenn wir nun eine Reihe von
Value
Elementen mit derselbenName
Eigenschaft haben, sollten sie alle zu einem Wert zusammenfallen, der von zurückgegeben wirdDistinct
, oder? Mal schauen...Ausgabe:
Hmm, das hat nicht funktioniert, oder?
Was ist mit
GroupBy
? Versuchen wir das mal:Ausgabe:
Wieder: hat nicht funktioniert.
Wenn Sie darüber nachdenken, wäre es sinnvoll
Distinct
, einHashSet<T>
(oder ein gleichwertiges) internGroupBy
zu verwenden und so etwas wie einDictionary<TKey, List<T>>
internes zu verwenden. Könnte dies erklären, warum diese Methoden nicht funktionieren? Lass uns das versuchen:Ausgabe:
Ja ... fängt es an Sinn zu machen?
Aus diesen Beispielen geht hoffentlich klar hervor, warum es so wichtig ist,
GetHashCode
in jedeIEqualityComparer<T>
Implementierung ein geeignetes Element aufzunehmen.Ursprüngliche Antwort
Die Antwort von orip erweitern :
Hier können einige Verbesserungen vorgenommen werden.
Func<T, TKey>
statt nehmenFunc<T, object>
; Dies verhindert das Boxen von Werttypschlüsseln im tatsächlichenkeyExtractor
selbst.where TKey : IEquatable<TKey>
Einschränkung hinzufügen . Dies verhindert das Boxen imEquals
Aufruf (object.Equals
nimmt einenobject
Parameter an; Sie benötigen eineIEquatable<TKey>
Implementierung, um einenTKey
Parameter zu nehmen , ohne ihn zu boxen). Dies kann eindeutig eine zu strenge Einschränkung darstellen, sodass Sie eine Basisklasse ohne die Einschränkung und eine abgeleitete Klasse damit erstellen können.So könnte der resultierende Code aussehen:
quelle
StrictKeyEqualityComparer.Equals
Methode scheint dieselbe zu sein wieKeyEqualityComparer.Equals
. Funktioniert dieTKey : IEquatable<TKey>
EinschränkungTKey.Equals
anders?TKey
der Compiler , da es sich um einen beliebigen Typ handeln kann, die virtuelle Methode, beiObject.Equals
der Werttypparameter in Boxen gesetzt werden müssen, zint
. Im letzteren Fall wird jedoch, daTKey
die Implementierung eingeschränkt istIEquatable<TKey>
, dieTKey.Equals
Methode verwendet, für die kein Boxen erforderlich ist.StringKeyEqualityComparer<T, TKey>
.Wenn Sie die Gleichheitsprüfung anpassen möchten, möchten Sie in 99% der Fälle die zu vergleichenden Schlüssel definieren, nicht den Vergleich selbst.
Dies könnte eine elegante Lösung sein (Konzept aus Pythons Listensortiermethode ).
Verwendung:
Die
KeyEqualityComparer
Klasse:quelle
Func<T, int>
, um den Hash-Code für einenT
Wert anzugeben (wie z. B. in Rubens Antwort vorgeschlagen ). Andernfalls ist dieIEqualityComparer<T>
Implementierung, die Ihnen verbleibt, ziemlich fehlerhaft, insbesondere im Hinblick auf ihre Nützlichkeit in LINQ-Erweiterungsmethoden. In meiner Antwort finden Sie eine Diskussion darüber, warum dies so ist.Ich fürchte, es gibt keine solche Verpackung, die sofort einsatzbereit ist. Es ist jedoch nicht schwer, eine zu erstellen:
quelle
private readonly Func<T, int> _hashCodeResolver
, das ebenfalls im Konstruktor übergeben und in derGetHashCode(...)
Methode verwendet werden muss.obj.ToString().ToLower().GetHashCode()
stattobj.GetHashCode()
?IEqualityComparer<T>
ausnahmslos Hashing hinter den Kulissen verwendet wird (z. B. GroupBy, Distinct, Except, Join usw. von LINQ), und der MS-Vertrag in Bezug auf Hashing sind in dieser Implementierung fehlerhaft. Hier ist der Dokumentationsauszug von MS: "Implementierungen sind erforderlich, um sicherzustellen, dass der von der GetHashCode-Methode für x zurückgegebene Wert dem für y zurückgegebenen Wert entsprechen muss, wenn die Equals-Methode für zwei Objekte x und y true zurückgibt." Siehe: msdn.microsoft.com/en-us/library/ms132155Wie Dan Taos Antwort, jedoch mit einigen Verbesserungen:
Verlässt sich auf
EqualityComparer<>.Default
den eigentlichen Vergleich, um das Boxen fürstruct
implementierte Werttypen zu vermeidenIEquatable<>
.Seit seiner
EqualityComparer<>.Default
Verwendung explodiert es nicht weiternull.Equals(something)
.Bereitgestellter statischer Wrapper,
IEqualityComparer<>
der eine statische Methode zum Erstellen der Instanz des Vergleichers enthält - erleichtert das Aufrufen. Vergleichen Siemit
Es wurde eine Überladung hinzugefügt, die
IEqualityComparer<>
für den Schlüssel angegeben werden soll.Die Klasse:
Sie können es so verwenden:
Person ist eine einfache Klasse:
quelle
Mit Erweiterungen: -
quelle
Die Antwort von orip ist großartig.
Hier eine kleine Erweiterungsmethode, um es noch einfacher zu machen:
quelle
Ich werde meine eigene Frage beantworten. Um Wörterbücher als Mengen zu behandeln, scheint die einfachste Methode darin zu bestehen, Mengenoperationen auf dict.Keys anzuwenden und dann mit Enumerable.ToDictionary (...) wieder in Wörterbücher zu konvertieren.
quelle
Die Implementierung bei (deutscher Text) Implementierung von IEqualityCompare mit Lambda-Ausdruck kümmert sich um Nullwerte und verwendet Erweiterungsmethoden, um IEqualityComparer zu generieren.
Um einen IEqualityComparer in einer Linq-Union zu erstellen, müssen Sie nur schreiben
Der Vergleicher:
Sie müssen auch eine Erweiterungsmethode hinzufügen, um die Typinferenz zu unterstützen
quelle
Nur eine Optimierung: Wir können den sofort einsatzbereiten EqualityComparer für Wertvergleiche verwenden, anstatt ihn zu delegieren.
Dies würde auch die Implementierung übersichtlicher machen, da die tatsächliche Vergleichslogik jetzt in GetHashCode () und Equals () verbleibt, die Sie möglicherweise bereits überladen haben.
Hier ist der Code:
Vergessen Sie nicht, die Methoden GetHashCode () und Equals () für Ihr Objekt zu überladen.
Dieser Beitrag hat mir geholfen: c # vergleiche zwei generische Werte
Sushil
quelle
Die Antwort von orip ist großartig. Die Antwort von orip erweitern:
Ich denke, dass der Schlüssel der Lösung die Verwendung der "Erweiterungsmethode" ist, um den "anonymen Typ" zu übertragen.
Verwendung:
quelle
Dies ermöglicht die Auswahl einer Eigenschaft mit Lambda wie folgt:
.Select(y => y.Article).Distinct(x => x.ArticleID);
quelle
Ich kenne keine existierende Klasse, aber so etwas wie:
Hinweis: Ich habe dies noch nicht kompiliert und ausgeführt, daher liegt möglicherweise ein Tippfehler oder ein anderer Fehler vor.
quelle