2014-11-28 17 views
13

sto leggendo Effective C# e non v'è un commento su Object.GetHashCode() che non ho capito:Attuazione della Object.GetHashCode()

Object.GetHashCode() utilizza un campo interno della classe System.Object per generare il valore hash. Ad ogni oggetto creato viene assegnata una chiave oggetto univoca, memorizzata come numero intero, quando viene creata.
Queste chiavi iniziano da 1 e incrementano ogni volta che un nuovo oggetto di qualsiasi tipo ottiene creato. Il campo Identità oggetto è impostato nel costruttore System.Object e non può essere modificato in seguito. Object.GetHashCode() restituisce questo valore come codice hash per un dato oggetto.

Ho provato a guardare la documentazione di Object.GetHashCode() e non ho trovato alcuna informazione al riguardo.

ho scritto il semplice pezzo di codice per stampare il codice hash di oggetti appena generato:

using System; 

namespace TestGetHashCode 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      for (int i = 0; i < 100; i++) 
      { 
       object o = new object(); 
       Console.WriteLine(o.GetHashCode()); 
      } 
     } 
    } 
} 

i primi numeri che sono stati stampati sono stati:

37121646, 
45592480, 
57352375, 
2637164, 
41014879, 
3888474, 
25209742, 
26966483, 
31884011 

Il che non sembra fit che

Queste chiavi iniziano da 1 e aumentano ogni volta che viene creato un nuovo oggetto di qualsiasi tipo ... Object.GetHashCode() restituisce questo valore

Poi, al fine di trovare questo "campo interno nel System.Object" Ho provato ad utilizzare ReSharper decompiled sources ma il codice che ho trovato è stato

[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] 
[__DynamicallyInvokable] 
public virtual int GetHashCode() 
{ 
    return RuntimeHelpers.GetHashCode(this); 
} 

e ancora una volta l'utilizzo di fonti decompilati ho scoperto che RuntimeHelpers.GetHashCode era implementato come

[SecuritySafeCritical] 
[__DynamicallyInvokable] 
[MethodImpl(MethodImplOptions.InternalCall)] 
public static int GetHashCode(object o); 

seguente the MethodImpl attribute sembra che non riesco a visualizzare l'implementazione e questo è un morto en d per me.

Qualcuno può spiegare il commento dell'autore (la prima citazione)?

Qual è il campo interno all'interno della classe Object e come viene utilizzato per l'implementazione di Object.GetHashCode()?

+0

Questo può essere utile: http://stackoverflow.com/questions/720177/default-implementation -for-object-gethashcode –

+0

@Yuval Itzchakov: è un'ironia? Altrimenti, perché la pensi così? – zerkms

+1

@Yuval Itzchakov: perché pensi che 'GetHashCode()' debba restituire un ** identificativo univoco **? Si chiama 'Get * Hash * Code()' non 'Get * UniqueIdentifier *()' con lo scopo – zerkms

risposta

17

Ok, farei meglio a scriverlo. Il libro è molto impreciso. Il valore per Object.GetHashCode() viene generato all'interno del CLR e viene calcolato su richiesta, ogni volta che GetHashCode() viene chiamato la prima volta. Citerò il codice dalla distribuzione SSCLI20, CLR/src/vm/thread.h ha la funzione che produce il numero, sembra che questo (a cura per migliorare la leggibilità):

inline DWORD GetNewHashCode() 
{ 
    // Every thread has its own generator for hash codes so that we won't get into a 
    // situation where two threads consistently give out the same hash codes. 
    // Choice of multiplier guarantees period of 2**32 
    // see Knuth Vol 2 p16 (3.2.1.2 Theorem A). 
    DWORD multiplier = m_ThreadId*4 + 5; 
    m_dwHashCodeSeed = m_dwHashCodeSeed*multiplier + 1; 
    return m_dwHashCodeSeed; 
} 

Dopo di che è memorizzato nel cosiddetto blocco di sincronizzazione dell'oggetto, quindi le chiamate successive restituiscono lo stesso valore. Solo 26 dei 32 bit generati vengono effettivamente memorizzati, il blocco di sincronizzazione ha bisogno di spazio per alcuni bit di stato. Ancora abbastanza buono da generare un codice hash di altissima qualità, le collisioni sono piuttosto rare.

La presenza della variabile m_ThreadId in tale codice può utilizzare una spiegazione. Il seed del generatore di numeri casuali viene memorizzato per ogni singolo thread. Un trucco per evitare di dover prendere un lucchetto.

Il m_dwHashCodeSeed viene inizializzato nel costruttore Discussione in questo modo:

// Initialize this variable to a very different start value for each thread 
    // Using linear congruential generator from Knuth Vol. 2, p. 102, line 24 
    dwHashCodeSeed = dwHashCodeSeed * 1566083941 + 1; 
    m_dwHashCodeSeed = dwHashCodeSeed; 

con:

static DWORD dwHashCodeSeed = 123456789; 
+0

Grazie per la risposta. Da questo codice sembra che GetNewHashCode sia una funzione che dipende solo da m_ThreadId, quindi per lo stesso thread genera lo stesso codice hash in ogni chiamata. Cosa mi manca qui? – Belgi

+0

No, dipende da m_dwHashCodeSeed, aggiornato ogni volta che viene generato un nuovo codice hash. Ogni thread inizia con un seme diverso, un frammento di fondo. m_ThreadId aggiunge solo un ulteriore livello di casualità, Donald Knuth ha aiutato con quello. Non concentrarti troppo sui thread coinvolti in questo, è solo un trucco di codice senza blocco. Le serrature sono costose. C'è qualche trucco legato a due thread che chiamano GetHashCode allo stesso tempo, l'ho lasciato fuori per mantenerlo semi-comprensibile. –