用不同的属性覆盖 GetHashCode

Overriding GetHashCode with different properties

我有这个对象:

public class Foo  {
    public string MyOwnId { get; set; }
    public Guid FooGuid { get; } = Guid.NewGuid();
}

我希望Equals()只关心那些MyOwnId,否则他们永远不会相等。当一个 Foo 有一个 MyOwnId 我尝试使用它,否则我想使用 FooGuid.

因为 FooGuid 可能永远不会一样,我做了这样的事情:

public bool Equals(Foo foo) {
        if (foo== null) return false;
        return MyOwnId.Equals(foo.MyOwnId);
    }

    public override bool Equals(object obj) {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((Foo)obj);
    }

    public override int GetHashCode() {
        int hash = 13;
        hash = (hash*7) + (!string.IsNullOrEmpty(MyOwnId) ? MyOwnId.GetHashCode() : FooGuid.GetHashCode());
        return hash;
    }

这是做我想做的事情的正确方法吗?或者我是否还需要更改我的 Equals 方法,使其看起来与我的 GetHashCode 一样?例如:

public bool Equals(Foo foo) {
        if (foo == null) return false;
        if (string.IsNullOrEmpty(MyOwnId) || string.IsNullOrEmpty(foo.MyOwnId)) return false;
        return MyOwnId.Equals(foo.MyOwnId);
    }

哈希函数必须具有以下属性:

  • 如果两个对象比较相等,则每个对象的 GetHashCode 方法必须 return 相同的值。但是,如果两个对象不相等,则两个对象的 GetHashCode 方法不必 return 不同的值。
  • 对象的 GetHashCode 方法必须始终 return 相同的散列码,只要没有对确定对象的 Equals 方法的 return 值的对象状态进行修改。请注意,这仅适用于应用程序的当前执行,并且如果应用程序再次 运行,则可以 returned 不同的哈希码。
  • 为了获得最佳性能,哈希函数应该为所有输入生成均匀分布,包括高度聚集的输入。这意味着对对象状态的小修改应该导致对生成的散列代码进行大修改以获得最佳散列 table 性能。
  • 哈希函数的计算成本应该很低。
  • GetHashCode 方法不应抛出异常。

https://msdn.microsoft.com/en-us/library/system.object.gethashcode(v=vs.110).aspx

好吧,让我们看看。您对 EqualsGetHashCode 的实施是 错误的

EqualsGetHashCode 都不能抛出异常;反例是

  Foo A = new Foo();
  Foo B = new Foo() {
    MyOwnId = "bla-bla-bla",
  };

  // Throws an exception
  if (A.Equals(B)) {}

如果两个实例通过 Equals 相等,则这些实例必须具有相同的哈希码;反例是

  Foo A = new Foo() {
    MyOwnId = "",
  };

  Foo B = new Foo() {
    MyOwnId = "",
  };

  if (A.Equals(B)) {
    // Hashcodes must be equal and they are not
    Console.Write(String.Format("{0} != {1}", A.GetHashCode(), B.GetHashCode()));
  }

可能的(最简单的)实现

// since you've declared Equals(Foo other) let others know via interface implementation
public class Foo: IEquatable<Foo> { 
  public string MyOwnId { get; set; }
  public Guid FooGuid { get; } = Guid.NewGuid();

  public bool Equals(Foo other) {
    if (Object.ReferenceEquals(this, other))
      return true;
    else if (Object.ReferenceEquals(null, other))
      return false;
    else
      return String.Equals(MyOwnId, other.MyOwnId);
  }

  public override bool Equals(object obj) {
    return Equals(obj as Foo); // do not repeat youself: you've got Equals already
  }

  public override int GetHashCode() {
    // String.GetHashCode is good enough, do not re-invent a wheel
    return null == MyOwnId ? 0 : MyOwnId.GetHashCode(); 
  }
}

Or do I also need change my Equals method so it looks the same like my GetHashCode?

您更改 Equals 以匹配您希望解决平等问题的方式。你做到了。

您将 GetHashCode() 更改为键入相同的信息。在这种情况下:

public override int GetHashCode()
{
  return MyOwnId == null ? 0 : MyOwnId.GetHashCode();
}

顺便说一下,您的 Equals(object) 有点过于复杂了。我会使用:

public override bool Equals(object obj)
{
  return Equals(obj as Foo);
}

这将处理 obj 为空的情况传递给特定的 Equals()(它也必须处理它),处理 obj 不是 Foo 通过将 Equals() 传递给一个空值(无论如何都是假的)并将 obj 是从 Foo 派生的东西的处理传递给更具体的(再次,必须处理那个)。

ReferenceEquals 的快捷方式不值得在这里进行,因为只有一个字段被比较,并且它的比较将具有相同的 ReferenceEquals 快捷方式。尽管您没有处理 foo 作为专用 Foo 中的派生类型。如果 Foo 没有密封,你应该包括:

public bool Equals(Foo foo)
{
  return (object)foo != null &&
    foo.GetType() == GetType() &&
    MyOwnId.Equals(foo.MyOwnId);
}

如果 Foo 是密封的,那么应该省略 GetType() 比较。

如果 Equals() 的逻辑比这更复杂,那么:

public bool Equals(Foo foo)
{
  if ((object)foo == (object)this)
    return true;
  return (object)foo != null &&
    foo.GetType() == GetType() &&
    // Some more complicated logic here.
}

确实有好处,但同样应该在特定的重载而不是一般的覆盖中。

(在 == 重载中再次进行引用相等性检查更有益,因为他们必须考虑两个操作数都为 null 的可能性,因此他们不妨考虑它们都相同隐式包括这种情况)。