2012-01-05 15 views
37

Ten rodzaj wydaje się jak pytanie noob, ale nie mogłem znaleźć konkretnej odpowiedzi na to pytanie.HashSet umożliwia zdublowane wstawianie elementów - C#

mam tej klasy:

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

I używam to:

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

Jednak jestem w stanie dodać te same cytaty obiekt wielokrotnie. Na przykład, mój obiekt Cytatów może mieć "symbol" równy "A" i "rozszerzenie" równe "= n", a ten obiekt Cytatów pojawia się wiele razy w HashSet (oglądanie Hashset w trybie debugowania). Myślałem, że podczas wywoływania tego samego symb i ext, zwracane jest "false", a element nie zostanie dodany. Mam wrażenie, że ma coś wspólnego z porównywaniem obiektów Cytatów, gdy HashSet dodaje nowy obiekt. Każda pomoc będzie bardzo ceniona!

+0

Być może chcesz spojrzeć na HashTable lub nawet lepiej Dictionary MethodMan

+0

@ jpints14 co zrobić mieszkasz? zawartość ciągu lub lokalizacja pamięci? (lub inne) – Adrian

+0

Przez "możliwość wielokrotnego dodawania tego samego obiektu Cytaty" ma Pan na myśli dodanie tej samej instancji lub dodanie identycznych instancji? –

Odpowiedz

47

Zgaduję, że tworzysz nowy Quotes o tych samych wartościach. W tym przypadku nie są równe. Jeśli należy je uznać za równe, należy zastąpić metody Equals i 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

Należy zauważyć, że jeśli symbol lub rozszerzenie może mieć wartość zerową, kod GetHashCode musi obsłużyć to i nie ulec awarii. –

+0

Mam sprawdzić, zanim porównanie jest kiedykolwiek potrzebne, ale dzięki za końcówkę – jpints14

+3

Należy pamiętać, że dla typów pól innych niż 'string's,' int''s lub innych typów wartości lub klas zapieczętowanych, należy użyć 'q! = null && q.symbol.Equals (this.symbol) && q.extension.Equals (this.extension) 'zamiast używać' == ', ponieważ' == 'nie jest polimorficzne (tzn. jeśli podklasy definiują' operator == ', podstawowa klasa '' orperator == 'będzie nadal używana, podczas gdy podklasy mogą * nadpisywać * metodę' .Equals() ', więc będzie używana podklasa'' .Equals() '. Również' hash1^hash2' jest kiepską implementacją skrótu, ponieważ '" a "," b "' i '" b "," a "' mają taki sam skrót. Preferuj coś takiego jak '(hash1 + 7 * 13)^hash2'. –

19

myślałem, że podczas wywoływania values.Add(new Quotes(symb, ext)); z tym samym symb i EXT, „false” zostaną zwrócone i element nie zostanie dodana.

Tak nie jest.

HashSet użyje GetHashCode i Equals do określenia równości twoich obiektów. W tej chwili, ponieważ nie zastąpisz tych metod w Quotes, zastosowana zostanie domyślna równość referencyjna System.Object. Za każdym razem, gdy dodajesz nową ofertę, jest to unikalna instancja obiektu, więc HashSet widzi ją jako unikalny obiekt.

Jeśli zastąpisz Object.Equals i Object.GetHashCode, będzie działać zgodnie z oczekiwaniami.

5

HashSets najpierw porównują wpisy na podstawie ich skrótu, który jest obliczany przez GetHashCode.
Domyślna implementacja zwraca kod skrótu oparty na samym obiekcie (różni się między poszczególnymi wystąpieniami).

Tylko jeśli skróty są takie same (bardzo nieprawdopodobne w przypadku skrótów opartych na wystąpieniach), metoda Równa jest wywoływana i używana do definitywnego porównania dwóch obiektów.

Trzeba opcji:

  • Zmień notowania do struktury
  • Zastąp GetHashCode i równa w cudzysłowie

Przykład:

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

Musisz również zastąpić 'Object.Equals' - Hashe nie mają gwarancji, że są unikatowe, więc obie metody są używane ... –

+0

Tak - zbyt wiele uwagi poświęciłem pisaniu odpowiedzi wystarczająco szybko :-D Właśnie dodałem, dzięki. – Matthias

+1

mmm - Nie sądzę, aby Twój obiekt Object.ReferenceEquals miał całkowitą rację ...;) Zasadniczo, w sposób jaki masz, za każdym razem "obj" jest obiektem Cytatów, powiesz, że nie jest równy (co jest tylko w ten sposób, że może być równy ...) –

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

.. jest dodanie tej samej instancji dwa razy, a po raz drugi zwróci wartość false.

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

.. dodaje dwa różne wystąpienia, które mają te same wartości dla pól publicznych.

Jak wspomniano gdzie indziej, przesłanianie równa się i GetHashCode to poprawi:

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

Jeśli krok debugowania kodu, można zauważyć, że values.Add wzywa obie Quotes.Equals i Quotes.GetHashCode.

+0

Co robi '^' do w twoim 'return (this.symbol.GetHashCode())^(this.extension.GetHashCode());'? czy mój pierwszy raz widzę, że to jest literówka? – Niklas

2

Wiem, że to trochę późno, ale wpadłem na ten sam problem i znaleźć niedopuszczalny spadek wydajności podczas wdrażania wybraną odpowiedź, szczególnie gdy masz dużo rekordów.

Zauważyłem, że o wiele szybciej jest przekształcenie tego procesu w dwuetapowy proces z użyciem Hashset i Tuple, a następnie przekształcenie za pomocą 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

Spróbuj porównać go z podejściem podobnym do zaakceptowanej odpowiedzi, ale także mając '' Cytaty' implementujące 'IEquatable ' i możesz uzyskać lepsze wyniki. Jeszcze lepsze wyniki są prawdopodobnie możliwe dzięki udoskonaleniu 'GetHashCode()'. –

3

Chciałem tylko naprawić coś w odpowiedzi Kendalla (nie mogę komentować z jakiegoś dziwnego powodu).

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

pamiętać, że funkcja XOR jest wyjątkowo podatne na zderzenie sposobem łączenia dwóch mieszań, zwłaszcza gdy są one zarówno z tego samego typu (od każdego obiektu, gdzie symbol == rozszerzenie będzie hash do 0). Nawet jeśli nie są tego samego typu lub raczej nie są sobie równe, jest to zła praktyka, a przyzwyczajenie się do niej może powodować problemy w różnych urządzeniach.

Zamiast mnożyć jeden hash z małą liczbą pierwszą, i dodać drugi, np:

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