为了确保平等正常运行,结构中需要覆盖哪些内容?


Answers:


90

来自msdn的示例

public struct Complex 
{
   double re, im;
   public override bool Equals(Object obj) 
   {
        return obj is Complex c && this == c;
   }
   public override int GetHashCode() 
   {
      return re.GetHashCode() ^ im.GetHashCode();
   }
   public static bool operator ==(Complex x, Complex y) 
   {
      return x.re == y.re && x.im == y.im;
   }
   public static bool operator !=(Complex x, Complex y) 
   {
      return !(x == y);
   }
}

我不知道是否woldn't更好的性能,使用Complex other = obj as Complex,然后检查other == null,而不是使用is,然后铸...
克莱门特

5
@克莱门特:你不能为一个结构做那件事;结果不能为空。您会得到一个编译错误。
马修·沃森

@MatthewWatson:我认为可以使用Complex? other = obj as Complex?,但可为空的类型通常不适合效率。
超级猫

1
@HaraldCoppoolse-值类型采用自然密封,因此无法MyComplex按您的建议派生a 。
M.Babcock

1
为什么obj不是SaveOptions op && this == op; ?
比利·杰克·奥康纳

45

您还应该实现IEquatable <T>。这是《框架设计指南》的节选:

不要在值类型上实现IEquatable。值类型上的Object.Equals方法会引起装箱,并且其默认实现不是很有效,因为它使用了染感。IEquatable.Equals可以提供更好的性能,并且可以实现,从而不会引起装箱。

public struct Int32 : IEquatable<Int32> {
    public bool Equals(Int32 other){ ... }
}

在实现IEquatable.Equals时,请遵循与重写Object.Equals相同的准则。有关覆盖Object.Equals的详细准则,请参见8.7.1节。


所以这仅用于值类型吗?(不是参考?)
UpTheCreek,2009年

1
因为在将引用类型作为对象传递时不需要装箱,所以ergo,IEquatable <T>不会提供任何好处。值类型通常完全复制到堆栈上(或复制到外部类型布局中),因此要获取对象引用并正确处理对象的生存期,需要将其装箱(包装有特殊类型)并复制堆 只有这样,对堆对象的引用才能传递给类似Object.Equals的函数。
gimpf

15

不幸的是,我没有足够的声誉来评论其他条目。因此,我在此处发布了对顶级解决方案的可能增强。

纠正我,如果我错了,但是上面提到的实现

public struct Complex 
{
   double re, im;
   public override bool Equals(Object obj) 
   {
      return obj is Complex && this == (Complex)obj;
   }
   public override int GetHashCode() 
   {
      return re.GetHashCode() ^ im.GetHashCode();
   }
   public static bool operator ==(Complex x, Complex y) 
   {
      return x.re == y.re && x.im == y.im;
   }
   public static bool operator !=(Complex x, Complex y) 
   {
      return !(x == y);
   }
}

有重大缺陷。我指的是

  public override int GetHashCode() 
   {
      return re.GetHashCode() ^ im.GetHashCode();
   }

XORing是对称的,因此Complex(2,1)和Complex(1,2)会给出相同的hashCode。

我们可能应该做一些类似的事情:

  public override int GetHashCode() 
   {
      return re.GetHashCode() * 17 ^ im.GetHashCode();
   }

7
哈希码冲突不一定是问题。实际上,您总是会发生碰撞的可能性(在通气孔/生日悖论上读取)在您的情况下,Complex(1,4)和Complex(4,1)发生碰撞(公认的碰撞较少),这取决于您的数据。哈希码用于快速清除99.999%的不需要的对象(例如,在字典中)。等式运算符拥有最终决定权。
DarcyThomas

话虽这么说,您在结构上拥有的属性越多,发生碰撞的机会就越大。这可能是更好的哈希算法:stackoverflow.com/a/263416/309634
DarcyThomas 2013年

9

大多数时候,您可以避免在结构中实现Equals和GetHashcode-因为编译器会自动对Value类型使用引用成员的按位内容+反射来实现。

看一下那篇文章: 哪种数据存储结构/类最好?

因此,为了易于使用,您仍然可以实现==和!=。

但是大多数时候,您可以避免实现Equals和GetHashcode。
在这种情况下,您必须实现Equals和GetHashCode,这是您不希望考虑的字段。
例如,随着时间的流逝而变化的字段,例如“人的年龄”或“汽车的瞬时速度”(如果要在同一位置的字典中找到对象,则对象的身份不应更改)

关于最好的代码


与手动实施相比,反射要慢得多。如果您关心性能,请务必手动编写。
卡罗利·奥兹瓦特(KárolyOzsvárt)

3

两者之间的基本区别在于,==运算符是静态的,即在编译时确定适当的调用方法,而在Equals实例上则是动态调用该方法。
定义两者可能是最好的做法,即使在结构的情况下,这也无关紧要,因为无法扩展结构(结构不能从另一个结构继承)。


1

仅出于完整性,我还建议重载Equals方法:

public bool Equals(Complex other) 
{
   return other.re == re && other.im == im;
}

这是真正的改进,因为没有输入的自变量发生装箱 Equals(Object obj)方法

使用值类型的一些最佳做法:

  • 使它们不变
  • 覆盖等于(以对象作为参数的那个);
  • 重载等于接受具有相同值类型的另一个实例(例如* Equals(Complex other));
  • 重载运算符==和!=;
  • 覆盖GetHashCode

来自这篇文章:http : //theburningmonk.com/2015/07/beware-of-implicit-boxing-of-value-types/

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.