2012-01-05 37 views
37

Este tipo de parece una pregunta novato, pero no pude encontrar una respuesta específica para esta pregunta.HashSet permite la inserción de elementos duplicados - C#

tengo esta clase:

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

Y estoy usando esto:

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

Sin embargo, yo soy capaz de agregar las mismas Cotizaciones objeto varias veces. Por ejemplo, mi objeto Quotes puede tener 'symbol' igual a 'A' y 'extension' igual a '= n', y este objeto Quotes aparece varias veces en el HashSet (viendo Hashset a través del modo de depuración). Yo había pensado que al llamar

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

con el mismo symb y ext, 'falso' serían devueltos y no se añadiría el elemento. Tengo la sensación de que tiene algo que ver con la comparación de objetos Quotes cuando el HashSet está agregando un nuevo objeto. ¡Cualquier ayuda sería muy apreciada!

+0

Tal vez habría que desee ver en HashTable o incluso mejor Dictionary MethodMan

+0

@ jpints14 lo haga ¿tienes hash? el contenido de la cadena o la ubicación de la memoria? (u otro) – Adrian

+0

Al "poder agregar el mismo objeto Quotes varias veces", ¿quiere decir agregar la misma instancia exacta o agregar instancias idénticas? –

Respuesta

47

Supongo que está creando un nuevo Quotes con los mismos valores. En este caso, no son iguales. Si se los considera iguales, anula los métodos Equals y 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

Tenga en cuenta que si el símbolo o la extensión pueden ser nulos, GetHashCode debe manejar eso y no bloquearse. –

+0

Tengo un cheque antes de que alguna vez se necesite una comparación, pero gracias por la sugerencia – jpints14

+3

Tenga en cuenta que para los tipos de campo que no sean 'string's,' int's u otros tipos de valores o clases selladas, debe usar 'q! = null && q.symbol.Equals (this.symbol) && q.extension.Equals (this.extension) 'en lugar de usar' == ', porque' == 'no es polimórfico (es decir, si las subclases definen un' operator == ', la clase base '' orperator ==' se seguirá utilizando, mientras que las subclases * pueden anular * el método '.Equals()', por lo que se usará la subclase '' .Equals() '. Además,' hash1^hash2' es una mala implementación hash, ya que '" a "," b "' y '" b "," a "', tienen el mismo hash. Prefiere algo como '(hash1 + 7 * 13)^hash2'. –

19

Pensé que al llamar al values.Add(new Quotes(symb, ext)); con el mismo símbolo sy ext, se devolvería 'falso' y no se agregaría el elemento.

Este no es el caso.

HashSet usará GetHashCode y Equals para determinar la igualdad de sus objetos. En este momento, ya que no está anulando estos métodos en Quotes, se usará la igualdad de referencia predeterminada de System.Object. Cada vez que agrega una nueva Cita, es una instancia de objeto única, por lo que HashSet lo ve como un objeto único.

Si invalida Object.Equals y Object.GetHashCode, funcionará como esperaba.

5

HashSets primero compara entradas basadas en su hash que se calcula por GetHashCode.
La implementación predeterminada devuelve un código hash basado en el objeto mismo (difiere entre cada instancia).

Solo si los valores hash son los mismos (muy improbable para hashes basados ​​en instancias), se llama al método Equals y se usa para comparar definitivamente dos objetos.

Usted tiene dos opciones:

  • Cambio Cotizaciones a una estructura
  • Anulación GetHashCode e iguales en Cotizaciones

Ejemplo:

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

También es necesario anular 'Object.Equals' - No se garantiza que los hashes sean únicos, por lo que se usan ambos métodos ... –

+0

Sí - me concentré demasiado en escribir la respuesta lo suficientemente rápido :-D Lo acabo de agregar, gracias. – Matthias

+1

mmm - No creo que su comprobación Object.ReferenceEquals sea correcta ...;) Básicamente, de la forma en que lo tiene, en cualquier momento que "obj" sea un objeto Quotes, usted dirá que no es igual (que es el única forma en que podría ser igual ...) –

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

.. es agregando la misma instancia dos veces, y devolverá falso la segunda vez.

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

.. está agregando dos instancias diferentes que tienen los mismos valores para los campos públicos.

Como se ha señalado en otras partes, anulando iguales y GetHashCode corregirá esto:

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()); 
    } 
} 

Si paso a depurar el código, se encuentra que values.Add llama a ambos Quotes.Equals y Quotes.GetHashCode.

+0

¿Qué hace el '^' en su 'return (this.symbol.GetHashCode())^(this.extension.GetHashCode());'? Es la primera vez que veo esto, ¿es esto un error tipográfico? – Niklas

2

Sé que esto es un poco tarde, pero me encontré con el mismo problema y encontré un impacto en el rendimiento inaceptable, mientras que la aplicación de la respuesta seleccionada especialmente cuando se tiene una gran cantidad de registros.

Me pareció mucho más rápido convertir esto en un proceso de dos pasos usando Hashset y Tuple y finalmente transformándome a través de un Seleccionar.

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

Intente compararlo con un enfoque como la respuesta aceptada, pero también tiene 'Quotes' implementando' IEquatable ', y puede obtener mejores resultados. Mejores resultados aún es posible a través de ajustar 'GetHashCode()' aún más. –

3

Sólo quería arreglar algo en la respuesta de Kendall (no puedo comentar por alguna extraña razón).

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

cuenta que la función XOR es una forma excepcionalmente colisión propensa de combinar dos hashes, especialmente cuando ambos son del mismo tipo (ya que cada objeto donde símbolo == extensión de comprobación aleatoria, 0). Incluso cuando no son del mismo tipo o es poco probable que sean iguales entre sí, esta es una mala práctica, y acostumbrarse a ella podría causar problemas en diferentes dispositivos.

En su lugar, multiplicar un hash con un pequeño número primo, y añadir el segundo, por ejemplo:

return 3 * this.symbol.GetHashCode() + this.extension.GetHashCode(); 
Cuestiones relacionadas