2012-01-05 26 views
37

Questo tipo di sembra una domanda noob, ma non sono riuscito a trovare una risposta specifica a questa domanda.HashSet consente l'inserimento di elementi duplicati - C#

ho questa classe:

public class Quotes{ 
    public string symbol; 
    public string extension 
} 

E sto usando questo:

HashSet<Quotes> values = new HashSet<Quotes>(); 

Tuttavia io sono in grado di aggiungere gli stessi Quotes oggetto più volte. Ad esempio, il mio oggetto Quotes può avere 'symbol' uguale a 'A' e 'extension' uguale a '= n', e questo oggetto Quotes appare più volte nel HashSet (visualizzazione di Hashset tramite la modalità di debug). Avevo pensato che chiamando il numero

values.Add(new Quotes(symb, ext)); 

con lo stesso symb e ext, "false" sarebbe stato restituito e l'elemento non sarebbe stato aggiunto. Ho la sensazione che abbia qualcosa a che fare con il confronto degli oggetti Quotes quando HashSet sta aggiungendo un nuovo oggetto. Qualsiasi aiuto sarebbe molto apprezzato!

+0

Forse si vorrebbe guardare HashTable o meglio ancora Dictionary MethodMan

+0

@ jpints14 cosa fare hai un hash? il contenuto della stringa o la posizione della memoria? (o altro) – Adrian

+0

Con "possibilità di aggiungere lo stesso oggetto Quotes più volte" intendi l'aggiunta della stessa istanza esatta o l'aggiunta di istanze identiche? –

risposta

47

Suppongo che si stia creando un nuovo Quotes con gli stessi valori. In questo caso non sono uguali. Se devono essere considerati uguali, sovrascrivere i metodi Equals e GetHashCode.

public class Quotes{ 
    public string symbol; 
    public string extension 

    public override bool Equals(object obj) 
    { 
     Quotes q = obj as Quotes; 
     return q != null && q.symbol == this.symbol && q.extension == this.Extension; 
    } 

    public override int GetHashCode() 
    { 
     return this.symbol.GetHashCode()^this.extension.GetHashCode(); 
    } 
} 
+17

Si noti che se il simbolo o l'estensione può essere null, il GetHashCode deve gestirlo e non bloccarsi. –

+0

Ho un controllo prima che un confronto sia mai necessario, ma grazie per il suggerimento – jpints14

+3

Nota che per tipi di campi diversi da 'string's,' int's o altri tipi di valore o classi sigillate, dovresti usare 'q! = null && q.symbol.Equals (this.symbol) && q.extension.Equals (this.extension) 'invece di usare' == ', perché' == 'non è polimorfico (cioè se le sottoclassi definiscono un' operatore == ', la classe base '' orperator == 'sarà ancora usata, mentre le sottoclassi possono * sovrascrivere * il metodo' .Equals() ', quindi verrà usata la sottoclasse' .Equals()' Inoltre, 'hash1^hash2' è una povera implementazione di hash, dal momento che '" a "," b "' e '" b "," a "', hanno lo stesso hash Preferiamo qualcosa come '(hash1 + 7 * 13)^hash2'. –

19

Avevo pensato che quando si chiamava values.Add(new Quotes(symb, ext)); con lo stesso symb e ext, si sarebbe restituito "false" e l'elemento non sarebbe stato aggiunto.

Questo non è il caso.

HashSet utilizzerà GetHashCode e Equals per determinare l'uguaglianza degli oggetti. In questo momento, poiché non stai sovrascrivendo questi metodi in Quotes, verrà utilizzata l'uguaglianza di riferimento predefinita di System.Object. Ogni volta che aggiungi una nuova citazione, si tratta di un'istanza di oggetto unica, quindi HashSet la vede come un oggetto unico.

Se si sostituisce Object.Equals e Object.GetHashCode, funzionerà come previsto.

5

Gli hashset prima confrontano le voci in base al loro hash che è calcolato da GetHashCode.
L'implementazione predefinita restituisce un codice hash basato sull'oggetto stesso (diverso da ogni istanza).

Solo se gli hash sono uguali (molto improbabile per gli hash basati su istanze), il metodo Equals viene chiamato e utilizzato per confrontare definitivamente due oggetti.

Bisogna opzioni:

  • Modifica virgolette per una struct
  • Override GetHashCode ed è uguale tra virgolette

Esempio:

public override int GetHashCode() 
{ 
    return (this.symbol == null ? 0 : this.symbol.GetHashCode()) 
    ^(this.extension == null ? 0 : this.extension.GetHashCode()); 
} 
public override bool Equals(object obj) 
{ 
    if (Object.ReferenceEquals(this, obj)) 
     return true; 

    Quotes other = obj as Quotes; 
    if (Object.ReferenceEquals(other, null)) 
     return false; 

    return String.Equals(obj.symbol, this.symbol) 
     && String.Equals(obj.extension, this.extension); 
} 
+2

Devi anche eseguire l'override di 'Object.Equals' - Gli hash non sono garantiti come unici, quindi entrambi i metodi sono usati ... –

+0

Sì - concentrato troppo sulla scrittura della risposta abbastanza velocemente MrGreen L'ho appena aggiunto, grazie. – Matthias

+1

mmm - Non penso che il tuo controllo Object.ReferenceEquals sia giusto ...;) Fondamentalmente, il modo in cui lo hai, ogni volta che "obj" è un oggetto Quotes, dirai che non è uguale (che è il solo il modo in cui potrebbe essere uguale ...) –

2
Quotes q = new Quotes() { symbol = "GE", extension = "GElec" }; 
values.Add(q); 
values.Add(q); 

.. è aggiungendo la stessa istanza due volte e restituirà false la seconda volta.

values.Add(new Quotes() { symbol = "GE", extension = "GElec" }); 
values.Add(new Quotes() { symbol = "GE", extension = "GElec" }); 

.. sta aggiungendo due diverse istanze che hanno gli stessi valori per i campi pubblici.

Come osservato elswhere, ignorando Equals e GetHashCode correggerà questo:

public class Quotes { 
    public string symbol; 
    public string extension; 

    public override bool Equals(object obj) { 
     if (!(obj is Quotes)) { return false; } 
     return (this.symbol == ((Quotes)obj).symbol) && 
       (this.extension == ((Quotes)obj).extension); 
    } 

    public override int GetHashCode() { 
     return (this.symbol.GetHashCode())^(this.extension.GetHashCode()); 
    } 
} 

Se l'utente passo il debug del codice, vi accorgerete che values.Add chiama entrambi Quotes.Equals e Quotes.GetHashCode.

+0

Cosa fa il comando '^' nel tuo 'return (this.symbol.GetHashCode())^(this.extension.GetHashCode());'? è la mia prima volta che vedo questo è un errore di battitura? – Niklas

2

So che questo è un pò in ritardo, ma ho incontrato lo stesso problema e trovato un calo di prestazioni inaccettabili durante l'implementazione della risposta selezionata soprattutto quando si hanno un sacco di dischi.

Ho trovato molto più veloce per trasformare questo in un processo in due fasi utilizzando Hashset e Tuple e infine trasformando tramite un Select.

public class Quotes{ 
    public string symbol; 
    public string extension 
} 

var values = new HashSet<Tuple<string,string>>(); 

values.Add(new Tuple<string,string>("A","=n")); 
values.Add(new Tuple<string,string>("A","=n")); 

// values.Count() == 1 

values.Select (v => new Quotes{ symbol = v.Item1, extension = v.Item2 }); 
+0

Prova a confrontarlo con un approccio come la risposta accettata, ma anche con "Quotes" che implementa "IEquatable ", e potresti ottenere risultati migliori. Risultati migliori sono probabilmente possibili grazie al perfezionamento del 'GetHashCode()'. –

3

Volevo solo aggiustare qualcosa nella risposta di Kendall (non posso commentare per qualche strano motivo).

return this.symbol.GetHashCode()^this.extension.GetHashCode(); 

noti che la funzione XOR è un modo estremamente collisione inclini di combinare due hash, soprattutto quando entrambi sono dello stesso tipo (poiché ogni oggetto in cui il simbolo == estensione hash in 0). Anche quando non sono dello stesso tipo o è improbabile che siano uguali tra loro, questa è una cattiva pratica e abituarsi ad essa potrebbe causare problemi a diversi dispositivi.

Invece, moltiplicare un hash con un piccolo numero primo, e aggiungere il secondo, ad esempio:

return 3 * this.symbol.GetHashCode() + this.extension.GetHashCode(); 
Problemi correlati