HashSet позволяет вставлять дубликаты элементов - C # - PullRequest
38 голосов
/ 05 января 2012

Это похоже на вопрос нуба, но я не смог найти ответ на этот вопрос специально.

У меня есть этот класс:

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

И я использую это:

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

Однако я могу добавить один и тот же объект Quotes несколько раз.Например, мой объект Quotes может иметь 'symbol', равный 'A', и 'extension', равный '= n', и этот объект Quotes появляется несколько раз в HashSet (просмотр Hashset в режиме отладки).Я думал, что при вызове

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

с теми же символами и ext, будет возвращено «false» и элемент не будет добавлен.У меня есть ощущение, что это как-то связано со сравнением объектов Quotes, когда HashSet добавляет новый объект.Любая помощь будет принята с благодарностью!

Ответы [ 6 ]

50 голосов
/ 05 января 2012

Я предполагаю, что вы создаете новый Quotes с теми же значениями. В этом случае они не равны. Если их следует считать равными, переопределите методы Equals и 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();
    }
}
19 голосов
/ 05 января 2012

Я думал, что при вызове values.Add(new Quotes(symb, ext)); с теми же символами и ext будет возвращено значение «false», а элемент не будет добавлен.

Это не так.

HashSet будет использовать GetHashCode и Equals для определения равенства ваших объектов. Прямо сейчас, поскольку вы не переопределяете эти методы в Quotes, будет использоваться равенство ссылок по умолчанию System.Object. Каждый раз, когда вы добавляете новую цитату, это уникальный экземпляр объекта, поэтому HashSet видит его как уникальный объект.

Если вы переопределите Object.Equals и Object.GetHashCode, все будет работать так, как вы ожидаете.

6 голосов
/ 05 января 2012

HashSets сначала сравнивает записи на основе их хеша, который рассчитывается по GetHashCode.
Реализация по умолчанию возвращает хеш-код на основе самого объекта (различается для каждого экземпляра).

Только если хэшите же (очень маловероятно для хешей, основанных на экземплярах), метод Equals вызывается и используется для однозначного сравнения двух объектов.

У вас есть варианты:

  • Изменить кавычки наstruct
  • Переопределить GetHashCode и равно в кавычках

Пример:

 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);
 }
4 голосов
/ 29 мая 2015

Просто хотел что-то исправить в ответе Кендалла (не могу комментировать по какой-то странной причине).

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

Обратите внимание, что функция xor является исключительно склонным к столкновению способом объединения двух хешей, особенно когдаоба они одного типа (поскольку каждый объект, в котором расширение символа == будет хэшироваться в 0).Даже если они не относятся к одному и тому же типу или вряд ли будут равны друг другу, это плохая практика, и привыкание к ней может вызвать проблемы в разных устройствах.

Вместо этого умножьте один хеш на маленькийпростое число и добавьте второе, например:

return 3 * this.symbol.GetHashCode() + this.extension.GetHashCode();
2 голосов
/ 07 марта 2015

Я знаю, что это немного поздно, но я столкнулся с той же проблемой и обнаружил неприемлемое снижение производительности при реализации выбранного ответа, особенно когда у вас много записей.это в два этапа с использованием Hashset и Tuple и, наконец, преобразование с помощью 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 });
2 голосов
/ 05 января 2012
Quotes q = new Quotes() { symbol = "GE", extension = "GElec" };
values.Add(q);
values.Add(q);

.. добавляет один и тот же экземпляр дважды и возвращает false во второй раз.

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

.. добавляет два разных экземпляра, которые имеют одинаковые значения для открытых полей.

Как отмечено выше, переопределение Equals и GetHashCode исправит это:

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

Если вы пошагово отлаживаете свой код, вы обнаружите эти значения. При вызове Add будут и Quotes.Equals, и Quotes.GetHashCode.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...