HashSet позволяет дублировать вставку элементов - С#

Этот вид кажется вопросом о нобе, но я не мог найти ответ на этот вопрос.

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

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

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

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

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

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

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

Ответ 1

Я предполагаю, что вы создаете новый 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();
    }
}

Ответ 2

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

Это не тот случай.

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

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

Ответ 3

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

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

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

  • Изменить котировки на структуру
  • Переопределить GetHashCode и Equals в комментариях

Пример:

 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

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

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

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

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

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

Ответ 5

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

Если вы выполните отладку своего кода, вы увидите, что values.Add вызывает как Quotes.Equals, так и Quotes.GetHashCode.

Ответ 6

Я знаю, что это немного поздно, но я столкнулся с той же проблемой и обнаружил неприемлемое поражение производительности при реализации выбранного ответа, особенно когда у вас много записей.

Я нашел гораздо быстрее превратить это в двухэтапный процесс с использованием 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 });