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