使用不同的属性覆盖 GetHashCode

本文关键字:属性 覆盖 GetHashCode | 更新日期: 2023-09-27 18:32:27

我有这个对象:

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

我想Equals()只关心那些有MyOwnId的人,否则他们永远不会平等。当FooMyOwnId时,我会尝试使用它,否则我想使用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

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

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

  Foo A = new Foo();
  Foo B = new Foo() {
    MyOwnId = "bla-bla-bla",
  };
  // Throws an exception
  if (A.Equals(B)) {}

如果两个实例相等,则 via 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(); 
  }
}

或者我是否需要更改我的 Equals 方法,使其看起来与我的 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为 null 的情况传递给特定Equals()(也必须处理它),通过将obj传递Equals() null 来处理不是Foo的东西(无论如何都是假的),并将处理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 的可能性,因此他们不妨考虑它们都是相同的,这隐式包含这种情况)。

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

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

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