Angesichts der Tatsache, dass Sammlungen gerne als festgelegtes Mitglied System.Collections.Generic.HashSet<>
akzeptieren null
, kann man sich fragen, wie der Hash-Code null
lauten soll. Es sieht so aus, als würde das Framework Folgendes verwenden 0
:
// nullable struct type
int? i = null;
i.GetHashCode(); // gives 0
EqualityComparer<int?>.Default.GetHashCode(i); // gives 0
// class type
CultureInfo c = null;
EqualityComparer<CultureInfo>.Default.GetHashCode(c); // gives 0
Dies kann bei nullbaren Aufzählungen (ein wenig) problematisch sein. Wenn wir definieren
enum Season
{
Spring,
Summer,
Autumn,
Winter,
}
dann kann der Nullable<Season>
(auch genannt Season?
) nur fünf Werte annehmen, aber zwei davon, nämlich null
und Season.Spring
, haben den gleichen Hash-Code.
Es ist verlockend, einen "besseren" Gleichheitsvergleich wie diesen zu schreiben:
class NewNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
public override bool Equals(T? x, T? y)
{
return Default.Equals(x, y);
}
public override int GetHashCode(T? x)
{
return x.HasValue ? Default.GetHashCode(x) : -1;
}
}
Aber gibt es einen Grund, warum der Hash-Code von null
sein sollte 0
?
EDIT / ADDITION:
Einige Leute scheinen zu denken, dass es um das Überschreiben geht Object.GetHashCode()
. Das ist es wirklich nicht. (Die Autoren von .NET hat eine Überschreibung von machen GetHashCode()
in der Nullable<>
Struktur , die ist relevant, though.) Ein benutzer geschrieben Umsetzung des parameterlos GetHashCode()
kann nie mit der Situation , wo das Objekt , dessen Hash - Code , den wir suchen , ist null
.
Hier geht es darum, die abstrakte Methode zu EqualityComparer<T>.GetHashCode(T)
implementieren oder die Schnittstellenmethode auf andere Weise zu implementieren IEqualityComparer<T>.GetHashCode(T)
. Jetzt, während ich diese Links zu MSDN erstelle, sehe ich, dass dort steht, dass diese Methoden ein ArgumentNullException
if auslösen, wenn ihr einziges Argument ist null
. Dies muss sicherlich ein Fehler bei MSDN sein? Keine der .NET-eigenen Implementierungen löst Ausnahmen aus. Das Werfen in diesem Fall würde effektiv jeden Versuch unterbrechen, null
zu a hinzuzufügen HashSet<>
. Es sei denn, HashSet<>
es handelt sich um etwas Außergewöhnliches, wenn es um einen null
Gegenstand geht (das muss ich testen).
NEUE BEARBEITUNG / ERGÄNZUNG:
Jetzt habe ich versucht zu debuggen. Mit HashSet<>
, kann ich das mit dem Standardgleichheitsvergleich bestätigen, die Werte Season.Spring
und null
werde in dem gleichen Eimer beenden. Dies kann durch sehr sorgfältige Prüfung der privaten Array-Mitglieder m_buckets
und festgestellt werden m_slots
. Beachten Sie, dass die Indizes von Natur aus immer um eins versetzt sind.
Der oben angegebene Code behebt dies jedoch nicht. Wie sich herausstellt, HashSet<>
wird der Gleichheitsvergleicher niemals gefragt, wann der Wert ist null
. Dies ist aus dem Quellcode von HashSet<>
:
// Workaround Comparers that throw ArgumentNullException for GetHashCode(null).
private int InternalGetHashCode(T item) {
if (item == null) {
return 0;
}
return m_comparer.GetHashCode(item) & Lower31BitMask;
}
Dies bedeutet, dass es zumindest für HashSet<>
nicht einmal möglich ist, den Hash von zu ändern null
. Stattdessen besteht eine Lösung darin, den Hash aller anderen Werte wie folgt zu ändern:
class NewerNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
public override bool Equals(T? x, T? y)
{
return Default.Equals(x, y);
}
public override int GetHashCode(T? x)
{
return x.HasValue ? 1 + Default.GetHashCode(x) : /* not seen by HashSet: */ 0;
}
}
Antworten:
Solange der Hash - Code für NULL - Werte zurückgegeben wird , ist konsistent für die Art, sollten Sie in Ordnung sein. Die einzige Voraussetzung für einen Hash-Code ist, dass zwei Objekte, die als gleich gelten, denselben Hash-Code verwenden.
Die Rückgabe von 0 oder -1 für null funktioniert, solange Sie eine auswählen und diese ständig zurückgeben. Nicht-Null-Hash-Codes sollten natürlich nicht den Wert zurückgeben, den Sie für Null verwenden.
Ähnliche Fragen:GetHashCode für Nullfelder?
Was sollte GetHashCode zurückgeben, wenn der Objektbezeichner null ist?
Die "Bemerkungen" dieses MSDN-Eintrags werden im Zusammenhang mit dem Hash-Code ausführlicher beschrieben. Ergreifend, wird die Dokumentation keine Abdeckung oder die Diskussion über Nullwert liefert überhaupt - nicht einmal in den Community - Inhalten.Um Ihr Problem mit der Aufzählung zu beheben, implementieren Sie entweder den Hash-Code erneut, um einen Wert ungleich Null zurückzugeben, fügen Sie einen standardmäßigen "unbekannten" Aufzählungseintrag hinzu, der null entspricht, oder verwenden Sie einfach keine nullbaren Aufzählungen.
Interessanter Fund übrigens.
Ein weiteres Problem, das ich dabei im Allgemeinen sehe, ist, dass der Hash-Code keinen 4-Byte- oder größeren Typ darstellen kann, der ohne mindestens eine Kollision nullbar ist (mehr, wenn die Typgröße zunimmt). Beispielsweise ist der Hash-Code eines int nur der int, sodass der gesamte int-Bereich verwendet wird. Welchen Wert in diesem Bereich wählen Sie für null? Was auch immer Sie auswählen, es kollidiert mit dem Hash-Code des Werts.
Kollisionen an und für sich sind nicht unbedingt ein Problem, aber Sie müssen wissen, dass sie vorhanden sind. Hash-Codes werden nur unter bestimmten Umständen verwendet. Wie in den Dokumenten zu MSDN angegeben, wird nicht garantiert, dass Hash-Codes unterschiedliche Werte für unterschiedliche Objekte zurückgeben. Dies sollte daher nicht erwartet werden.
quelle
Object.GetHashCode()
in Ihrer eigenen Klasse (oder Struktur) überschreiben , wissen Sie, dass dieser Code nur dann getroffen wird, wenn Personen tatsächlich eine Instanz Ihrer Klasse haben. Diese Instanz kann nicht seinnull
. Deshalb sollten Sie nicht anfangen , Überschreibung vonObject.GetHashCode()
mitif (this == null) return -1;
Es gibt einen Unterschied zwischen „Seinnull
“ und „ist ein Objekt einige Felder besitzen, dienull
“.T
, dann(T?)null
und(T?)default(T)
wird der gleiche Hash-Code (in der aktuellen Implementierung von .NET). Dies könnte geändert werden, wenn die Implementierer von .NET entweder den Hash-Code vonnull
oder den Hash-Code-Algorithmus von ändernSystem.Enum
.HashSet<>
) nicht funktioniert, den Hash-Code von zu ändernnull
.Denken Sie daran, dass der Hash-Code nur als erster Schritt zur Bestimmung der Gleichheit verwendet wird und niemals als De-facto-Bestimmung verwendet wird, ob zwei Objekte gleich sind.
Wenn die Hash-Codes von zwei Objekten nicht gleich sind, werden sie als ungleich behandelt (weil wir davon ausgehen, dass die nicht zugrunde liegende Implementierung korrekt ist - dh wir raten nicht darüber nach). Wenn sie denselben Hash-Code haben, sollten sie auf tatsächliche Gleichheit überprüft werden. In Ihrem Fall
null
schlagen der und der Enum-Wert fehl.Infolgedessen ist die Verwendung von Null so gut wie jeder andere Wert im allgemeinen Fall.
Sicher, es wird Situationen wie Ihre Aufzählung geben, in denen diese Null mit dem Hash-Code eines echten Werts geteilt wird . Die Frage ist, ob für Sie der winzige Aufwand eines zusätzlichen Vergleichs Probleme verursacht.
Wenn ja, definieren Sie Ihren eigenen Vergleicher für den Fall der Nullwert für Ihren bestimmten Typ und stellen Sie sicher, dass ein Nullwert immer einen Hashcode liefert, der (natürlich!) Immer gleich ist, und einen Wert, der vom Basiswert nicht geliefert werden kann Typ's eigener Hash-Code-Algorithmus. Für Ihre eigenen Typen ist dies machbar. Für andere - viel Glück :)
quelle
Es muss nicht haben zu Null - Sie können es machen könnte 42 , wenn Sie es wollten.
Alles, was zählt, ist die Konsistenz während der Ausführung des Programms.
Dies ist nur die offensichtlichste Darstellung, da sie
null
intern häufig als Null dargestellt wird. Wenn Sie also beim Debuggen einen Hash-Code von Null sehen, werden Sie möglicherweise aufgefordert zu denken: "Hmm ... war dies ein Null-Referenzproblem?"Beachten Sie, dass, wenn Sie eine Zahl wie verwenden
0xDEADBEEF
, jemand sagen könnte, dass Sie eine magische Zahl verwenden ... und Sie wären es auch. (Man könnte sagen, Null ist auch eine magische Zahl, und Sie hätten irgendwie Recht ... außer dass sie so weit verbreitet ist, dass sie eine Ausnahme von der Regel darstellt.)quelle
Gute Frage.
Ich habe gerade versucht, dies zu codieren:
und führen Sie dies wie folgt aus:
es kehrt zurück
null
wenn ja, stattdessen normal
es kehrt
0
wie erwartet zurück oder einfacher Frühling, wenn wir es vermeiden, zu werfenint
.Also .. wenn Sie Folgendes tun:
BEARBEITEN
Von MSDN
Wenn zwei Objekte gleich sind, muss die GetHashCode-Methode für jedes Objekt denselben Wert zurückgeben. Wenn jedoch zwei Objekte nicht gleich verglichen werden, müssen die GetHashCode-Methoden für die beiden Objekte keine unterschiedlichen Werte zurückgeben
Mit anderen Worten: Wenn zwei Objekte denselben Hash - Code haben, bedeutet nicht , dass sie gleich sind, weil wirkliche Gleichheit bestimmt wird durch Equals .
Nochmals von MSDN:
quelle
Es hätte überhaupt alles sein können. Ich stimme eher zu, dass 0 nicht unbedingt die beste Wahl war, aber es ist eine, die wahrscheinlich zu den wenigsten Fehlern führt.
Eine Hash-Funktion muss unbedingt denselben Hash für denselben Wert zurückgeben. Sobald es eine Komponente gibt, die dies tut, ist dies wirklich der einzig gültige Wert für den Hash von
null
. Wenn es dafür eine Konstante gäbe, wie hm,object.HashOfNull
dannIEqualityComparer
müsste jemand, der eine implementiert , wissen, um diesen Wert zu verwenden. Wenn sie nicht darüber nachdenken, ist die Wahrscheinlichkeit, dass sie 0 verwenden, etwas höher als jeder andere Wert, denke ich.Wie oben erwähnt, denke ich, dass es völlig unmöglich ist, einen Punkt zu machen, nur weil es Typen gibt, die bereits der Konvention folgen, dass der Hash von Null 0 ist.
quelle
EqualityComparer<T>.GetHashCode(T)
für einen bestimmten Typ implementiert, derT
es erlaubtnull
, muss man etwas tun , wenn das Argument istnull
. Sie könnten (1) werfenArgumentNullException
, (2) zurückkehren0
oder (3) etwas anderes zurückgeben. Ich nehme Ihre Antwort für eine Empfehlung,0
in dieser Situation immer zurückzukehren ?Der Einfachheit halber ist es 0. Es gibt keine so harte Anforderung. Sie müssen nur die allgemeinen Anforderungen der Hash-Codierung sicherstellen.
Zum Beispiel müssen Sie sicherstellen , dass , wenn zwei Objekte gleich sind, ihre Hashcodes muss immer gleich sein. Daher müssen unterschiedliche Hashcodes immer unterschiedliche Objekte darstellen (dies ist jedoch nicht unbedingt umgekehrt: Zwei unterschiedliche Objekte haben möglicherweise denselben Hashcode, auch wenn dies häufig vorkommt, ist dies keine Hash-Funktion von guter Qualität - sie hat keine gute Kollisionsfestigkeit).
Natürlich habe ich meine Antwort auf Anforderungen mathematischer Natur beschränkt. Es gibt auch .NET-spezifische technische Bedingungen, die Sie hier lesen können . 0 für einen Nullwert gehört nicht dazu.
quelle
Dies könnte also durch die Verwendung eines
Unknown
Enum-Werts vermieden werden (obwohl es etwas seltsam erscheintSeason
, wenn a unbekannt ist). So etwas würde dieses Problem negieren:Dann hätten Sie eindeutige Hash-Code-Werte für jede Saison.
quelle
Persönlich finde ich die Verwendung von nullbaren Werten etwas umständlich und versuche, sie zu vermeiden, wann immer ich kann. Ihr Problem ist nur ein weiterer Grund. Manchmal sind sie zwar sehr praktisch, aber meine Faustregel lautet, Werttypen möglichst nicht mit Null zu mischen, nur weil diese aus zwei verschiedenen Welten stammen. In .NET Framework scheinen sie dasselbe zu tun - viele Werttypen bieten eine
TryParse
Methode, mit der Werte von keinem Wert getrennt werden können (null
).In Ihrem speziellen Fall ist es einfach, das Problem zu beseitigen, da Sie mit Ihrem eigenen
Season
Typ umgehen .(Season?)null
Für mich bedeutet "Saison ist nicht angegeben", wie wenn Sie ein Webformular haben, in dem einige Felder nicht benötigt werden. Meiner Meinung nach ist es besser, diesen speziellen 'Wert' an sich anzugeben,enum
als etwas klobig zu verwendenNullable<T>
. Es ist schneller (kein Boxen) leichter zu lesen (Season.NotSpecified
vsnull
) und löst Ihr Problem mit Hash-Codes.Natürlich ist es für andere Typen, wie
int
Sie die Wertdomäne nicht erweitern können, nicht immer möglich, einen der Werte als speziell zu bezeichnen. Aber mitint?
Hash-Code ist die Kollision, wenn überhaupt, ein viel kleineres Problem.quelle
Nullable<>
Struktur einfügen (auf den dasHasValue
Mitglied dann gesetzt wirdtrue
). Sind Sie sicher, dass das Problem bei wirklich kleiner istint?
? In den meisten Fällen werden nur wenige Werte von verwendetint
, und dann entspricht dies einer Aufzählung (die theoretisch viele Mitglieder haben kann).int
ist dies sinnvoller. Natürlich variieren die Vorlieben.quelle