2016-09-25 3 views
4

traf ich ein paar Plätze online, wo Code in etwa so aussah:Verwenden Sie F # 's Hash-Funktion in GetHashCode() böse?

[<CustomEquality;NoComparison>] 
type Test = 
    | Foo 
    | Bar 
    override x.Equals y = 
     match y with 
     | :? Test as y' -> 
      match y' with 
      | Foo -> false 
      | Bar -> true // silly, I know, but not the question here 
     | _ -> failwith "error" // don't do this at home 

    override x.GetHashCode() = hash x 

Aber wenn ich die oben in FSI ausführen, wird die Abfrage nicht zurück, wenn entweder ich hash foo auf einer Instanz von Test oder anrufen, wenn ich Rufen Sie foo.GetHashCode() direkt an.

let foo = Test.Foo;; 
hash foo;; // no returning to the console until Ctrl-break 
foo.GetHashCode();; // no return 

konnte ich nicht ohne weiteren Beweis, aber es legt nahe, dass hash x Anrufe GetHashCode() auf dem Objekt, das bedeutet, dass der obige Code gefährlich ist. Oder spielt nur FSI?

Ich dachte, Code wie oben bedeutet nur "bitte benutzerdefinierte Gleichheit implementieren, aber lassen Sie die Hash-Funktion als Standard".

Ich habe inzwischen dieses Muster anders implementiert, frage mich aber immer noch, ob ich richtigerweise davon ausgehe, dass hash nur GetHashCode() aufruft, was zu einer ewigen Schleife führt.


Als Nebenwirkung, kehrt sofort Gleichheit innerhalb FSI mit, was darauf hindeutet, dass es entweder nicht GetHashCode() vor dem Vergleich nicht nennen, oder es tut etwas anderes. Update: Dies ist sinnvoll, wie im obigen Beispiel x.Equals nicht GetHashCode() aufrufen, und der Gleichheitsoperator ruft in Equals, nicht in GetHashCode().

Antwort

5

Es ist nicht ganz so einfach, wie die hash Funktion einfach ein Wrapper für GetHashCode zu sein, aber ich kann Sie bequem sagen, dass es auf jeden Fall nicht sicher ist, die Umsetzung zu verwenden: override x.GetHashCode() = hash x.

Wenn Sie die hash Funktion durch verfolgen, erhalten Sie here up:

let rec GenericHashParamObj (iec : System.Collections.IEqualityComparer) (x: obj) : int = 
    match x with 
    | null -> 0 
    | (:? System.Array as a) -> 
     match a with 
     | :? (obj[]) as oa -> GenericHashObjArray iec oa 
     | :? (byte[]) as ba -> GenericHashByteArray ba 
     | :? (int[]) as ba -> GenericHashInt32Array ba 
     | :? (int64[]) as ba -> GenericHashInt64Array ba 
     | _ -> GenericHashArbArray iec a 
    | :? IStructuralEquatable as a ->  
     a.GetHashCode(iec) 
    | _ -> 
     x.GetHashCode() 

Sie können hier sehen, dass die Wild-Card-Fall ruft x.GetHashCode(), daher ist es sehr gut möglich, sich in einer Endlosschleife zu finden.

Der einzige Fall, den ich sehen könnte, wo Sie hash in einer Implementierung von GetHashCode() verwenden könnten, wäre, wenn Sie einige Mitglieder eines Objekts manuell hashen, um einen Hash-Code zu erzeugen.

Es gibt ein (sehr altes) Beispiel für die Verwendung von hash innerhalb GetHashCode() auf diese Weise in Don Syme's WebLog.


Übrigens ist das nicht das einzige, was an dem von Ihnen geposteten Code unsicher ist.

Überschreibungen für object.Equals dürfen absolut keine Ausnahmen auslösen. Wenn die Typen nicht übereinstimmen, müssen sie false zurückgeben. Dies ist in System.Object eindeutig dokumentiert.

Implementierungen von Equals dürfen keine Ausnahmen auslösen; sie sollten immer einen Wert zurückgeben. Wenn beispielsweise obj null ist, sollte die Equals-Methode den Wert false zurückgeben, statt eine ArgumentNullException auszulösen.

(Source)

+0

_ "wäre, wenn Sie einige Mitglieder eines Objekts manuell hashen" _, ja, dies begann tatsächlich mit dem Erstellen eines vergleichbaren Funktionstyps, ähnlich wie 'string * (' T -> 'U) ', wo in der Hash-Überschreibung Ich habe 'hash s' in der Zeichenfolge aufgerufen (also keine unendliche Rekursion dort). Aber als ich diese Threads online sah, dachte ich mir, hey, lass es uns ausprobieren ... Führe zu dieser Frage. – Abel

+0

Danke für den Zeiger auf den Quellcode. Und über Ihren Kommentar zu der Ausnahme: Sie haben Recht, schlechter Beispielcode ... Ich habe herumalbern. – Abel

+0

@Abel Ja, ich dachte, du wüsstest das wahrscheinlich, aber ich dachte es lohnt sich für andere Leute, die diese Frage/Antwort sehen, da es einer dieser üblichen kleinen Fehler ist, der ziemlich leicht zu machen ist. – TheInnerLight

5

Wenn die GetHashCode() Methode außer Kraft gesetzt wird, dann wird die hash operator dass verwenden:

generic Hashfunktion [Der hash Operator A ist], entworfen gleiche Hash-Werte für Gegenstände zurückzugeben, die entsprechend die = gleich sind Operator. Standardmäßig wird strukturelles Hashing für F # union-, Record- und Tuple-Typen verwendet, um den gesamten Inhalt des Typs zu hashen. Das genaue Verhalten der Funktion kann von Typ zu Typ angepasst werden, indem System.Object.GetHashCode für jeden Typ implementiert wird.

Also ja, das ist eine schlechte Idee und es macht Sinn, dass es zu einer Endlosschleife führen würde.

+0

_ "Das genaue Verhalten der Funktion kann für jeden Typen durch die Implementierung System.Object.GetHashCode an einer Typ-by-Typ-Basis eingestellt werden." _, Bemerkte ich, dass auch aber es heißt nicht, dass es 'GetHashCode()' aufrufen wird. Das heißt, es könnte durchaus vernünftig sein, 'x.GetHashCode() = zu schreiben, wenn x <0 dann 0 else hash x '(d. H. Wenn etwas unter Null als gleich anzusehen ist). – Abel

+0

Eigentlich hätte ich erwartet, dass 'hash' stattdessen' base.GetHashCode() 'aufruft, was _nicht_ zu unendlicher Rekursion führen würde. – Abel

+0

@Abel Es besagt, dass Sie ändern können, wie sich die 'hash' -Funktion für einen Typ verhält, indem Sie' GetHashCode' überschreiben. Wie würde sich sein Verhalten ändern, wenn es 'GetHashCode()' nicht aufruft? Es klingt, als würden Sie diesen Satz so interpretieren, dass er bedeutet: "Sie können die Funktion' hash' im überschreibenden 'GetHashCode' verwenden, was das Gegenteil von dem ist, was sie sagt. – JLRishe