HashSet允许重复项目插入 - C#

时间:2012-01-05 18:15:31

标签: c# .net hashset

这种看起来像是一个菜鸟问题,但我没有具体找到这个问题的答案。

我有这堂课:

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

我正在使用它:

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

但是我可以多次添加相同的Quotes对象。例如,我的Quotes对象可能具有等于'A'的'symbol'和'= n'的'extension',并且此Quotes对象在HashSet中出现多次(通过调试模式查看Hashset)。在调用

时我曾想过
values.Add(new Quotes(symb, ext));

使用相同的symb和ext,将返回“false”并且不会添加该元素。我感觉它与HashSet添加新对象时比较Quotes对象有关。任何帮助将不胜感激!

7 个答案:

答案 0 :(得分:50)

我猜你正在创建一个具有相同值的新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();
    }
}

答案 1 :(得分:19)

  

我原以为在使用相同的symb和ext调用values.Add(new Quotes(symb, ext));时,会返回'false'并且不会添加该元素。

事实并非如此。

HashSet将使用GetHashCodeEquals来确定对象的相等性。现在,由于您未在Quotes中覆盖这些方法,因此将使用默认的System.Object引用相等性。每次添加新的Quote时,它都是一个唯一的对象实例,因此HashSet将其视为唯一对象。

如果您覆盖Object.EqualsObject.GetHashCode,它将按预期工作。

答案 2 :(得分:6)

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

答案 3 :(得分:4)

只是想在肯德尔的答案中解决问题(不能因某些奇怪的原因发表评论)。

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

请注意,xor函数是一种特别容易碰撞的组合两个哈希的方法,特别是当它们都是相同类型时(因为symbol == extension的每个对象都会哈希为0)。即使它们不是同一类型或不太可能彼此相等,这也是不好的做法,习惯它可能会导致不同设备出现问题。

相反,将一个散列与一个小素数相乘,然后加上第二个,例如:

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

答案 4 :(得分:2)

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。

答案 5 :(得分:2)

我知道这有点晚了,但我遇到了同样的问题,并且在实施所选答案时发现了令人无法接受的性能损失,尤其是当您有大量记录时。

我发现使用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 });

答案 6 :(得分:0)

有人建议我重写Equals()和GetHashCode()不是一个好习惯。

类是引用类型,而结构是值类型。更改为结构将允许按值进行相等比较,从而使相同的符号/扩展名相等。

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

public static void Main()
{
    var hashSet = new HashSet<Quotes>();

    hashSet.Add(new Quotes { symbol = "aaa", extension = "bbb" });
    hashSet.Add(new Quotes { symbol = "aaa", extension = "bbb" });

    Console.WriteLine(hashSet.Count);
}

输出为1。