C# 如何在不显式类型转换的情况下调用基本 class == 运算符?

C# How to call the base class == operator, without explicitly typecasting?

我有一个基础 class (TFoo) 和后代 class (TBar);我正在覆盖两者的 == 运算符。我希望后代 class 检查自己的字段,并调用基 class 的 == 运算符,以便基 class 进行自己的检查。

在下面的代码中,您将看到在 TBar == operator 中,我将类型转换为基数 class 以检查基数 class 是否相等,如下所示:(TFoo)a == (TFoo)b .

(这似乎有效!希望我在测试中没有遗漏任何陷阱。)

不过,我正在寻找一种更优雅的方法来做到这一点。例如,(base)a == (base)ba base.== bbase.==(a, b)a.base.Equals(b) 或其他。

显然上面的例子是行不通的,可能看起来很可笑;如前所述,(TFoo)a == (TFoo)b 确实可以正常工作。我正在寻找一种无需明确命名 TFoo class.

的方法

编辑:感谢所有精彩的回复!我修改了下面的原始代码,直接比较.GetType();我删除了几个人指出的愚蠢和危险的 .Name

class TFoo
{
    public int foo;

    public static bool operator ==(TFoo a, TFoo b)
    {
        return a.GetType() == b.GetType()
            && a.foo == b.foo;
    }

    public static bool operator !=(TFoo a, TFoo b)
    {
        return !(a == b);
    }

    public override bool Equals(object obj)
    {
        return (obj is TFoo) && this == (TFoo)obj;
    }

    public override int GetHashCode()
    {
        return this.foo;
    }
}

class TBar : TFoo
{
    public int bar;

    public static bool operator ==(TBar a, TBar b)
    {
        return (TFoo)a == (TFoo)b
            && a.bar == b.bar;
    }

    public static bool operator !=(TBar a, TBar b)
    {
        return !(a == b);
    }

    public override bool Equals(object obj)
    {
        return (obj is TBar) && this == (TBar)obj;
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
}

我认为您可以简单地在 TFoo 中覆盖 Equals 并从 == 中调用它,然后在 TBar 中再次覆盖它。然后,您只需从 TBar.Equals 调用 base.Equals 来检查基本属性

会简化我认为的逻辑

class TFoo
{
    public int foo;

    public static bool operator ==(TFoo a, TFoo b)
    {
        return a.Equals(b);
    }  

    public override bool Equals(object obj)
    {
        return this.GetType() == obj.GetType()
            && this.foo == ((TFoo)obj).foo;
    }

    //your code
}


class TBar : TFoo
{
    public int bar;

    public static bool operator ==(TBar a, TBar b)
    {
        return a.Equals(b);
    }

    public override bool Equals(object obj)
    {
        return (obj is TBar) && this.bar == ((TBar)obj).bar && base.Equals(obj);
    }

    //your code
}

您必须进行转换 (TFoo)a == (TFoo)b 的原因是 == 运算符是静态的。也就是说,不考虑运行时类型,而是使用编译时已知的静态类型的 == 运算符,如果你不进行强制转换的话。相反,Equals 是一个实例成员,其实现在运行时由调用者对象的实际类型确定。如果您想要动态行为,请将 == 运算符基于 Equals.

我们希望 == 运算符是对称的。即,a == b 应与 b == a 相同。但我们不一定期望 a.Equals(b)b.Equals(a) 相同,因为如果 ab 具有不同的类型,那么 Equals 的不同实现将是叫。因此,我建议通过调用 a.Equals(b) && b.Equals(a)(并处理空值)来实现 == 运算符。

class Foo
{
    public int foo;

    public override bool Equals(object other)
    {
        return other is Foo otherFoo && foo.Equals(otherFoo.foo);
    }

    public static bool operator ==(Foo first, Foo second)
    {
        if ((object)first == null) {
            return (object)second == null;
        }
        return first.Equals(second) && second.Equals(first);
    }

    public static bool operator !=(Foo first, Foo second)
    {
        return !(first == second);
    }

    public override int GetHashCode()
    {
        unchecked {
            return foo.GetHashCode();
        }
    }
}

以及派生的class

class Bar : Foo
{
    public int bar;

    public override bool Equals(object other)
    {
        return other is Bar otherBar && bar.Equals(otherBar.bar) && base.Equals(other);
    }

    public static bool operator ==(Bar first, Bar second)
    {
        if ((object)first == null) {
            return (object)second == null;
        }
        return first.Equals(second) && second.Equals(first); // In case one is more derived.
    }

    public static bool operator !=(Bar first, Bar second)
    {
        return !(first == second);
    }

    public override int GetHashCode()
    {
        unchecked {
            return (base.GetHashCode() * 53) ^ bar.GetHashCode();
        }
    }
}

在 C# 中正确而优雅地实现相等性是不必要的困难;这是我坚信设计不足的语言领域。

我的建议是:

首先,修复您的实现。它在很多方面都被破坏了:

  • == 永远不会崩溃,但如果 == 被赋予 null 操作数
  • ,您的实现会立即崩溃
  • 类型已经使用值语义进行比较;你永远不应该通过它们的名字来比较两种类型的相等性!您可以将来自两个不同程序集的两种类型同名,或者将来自同一程序集的相同类型的两种类型加载到不同的上下文中!通过比较类型来比较类型。如果意图是说 ab 必须是完全相同的类型然后说 a.GetType() == b.GetType(),在你知道两者都不是 null
  • 之后

修复实施后,改进它:

  • 尽可能便宜、轻松地购买。始终首先检查引用相等性,使用 object.ReferenceEquals 向 reader 表明这就是您正在做的事情。
  • 相反:如果您要检查引用相等性,请显式调用 object.ReferenceEquals,并避免在您打算进行引用相等性时意外调用 operator == 的许多愚蠢错误。
  • 编写一个方法,它是单一事实来源,用于给定类型的相等性,然后调用该方法——直接或间接地——来自所有其他实现平等的方法。由于您的意图是使派生 class 的实现依赖于基 class 的细节,使其成为受保护的虚拟方法.
  • 既然你已经完成了所有这些工作,那么你最好在你的类型上实现 IEquatable<T>

我会这样做:

class Foo : IEquatable<Foo> 
{
  public override bool GetHashcode() { ... }
  protected virtual bool EqualsImplementation(Foo f) 
  {
    if (object.ReferenceEquals(this, f)) return true;
    if (object.ReferenceEquals(f, null)) return false;
    ... We now have this and f as valid, not ref equal Foos.
    ... implement the comparison logic here
  }
  // Now implement Equals(object) by using EqualsImplementation():
  public bool Equals(object f) => 
    (!object.ReferenceEquals(f, null)) &&  
    (f.GetType() == this.GetType()) &&
    this.EqualsImplementation((Foo)f);
  // Now implement Equals(Foo) using Equals(object)
  public bool Equals(Foo f) => this.Equals((object)f);
  // Now implement Equals(Foo, Foo) using Equals(Foo)
  public static bool Equals(Foo f1, Foo f2) =>
    object.ReferenceEquals(f1, null) ? 
      object.ReferenceEquals(f2, null) :
      f1.Equals(f2);
  // You see how this goes. Every subsequent method uses
  // the correctness of the previous method to ensure its
  // correctness in turn!
  public static bool operator ==(Foo f1, Foo f2) => 
    Equals(f1, f2);
  public static bool operator !=(Foo f1, Foo f2) => 
    !(f1 == f2);
  ...
}

现在派生的一切都很容易了 class:

class Bar : Foo, IEquatable<Bar> 
{
  public override bool GetHashcode() { ... }
  protected override bool EqualsImplementation(Foo f) 
  {
    // Again, take easy outs when you find them.
    if (object.ReferenceEquals(this, f)) return true;
    Bar b = f as Bar;
    if (object.ReferenceEquals(b, null)) return false;
    if (!base.EqualsImplementation(f)) return false;
    ... We have b and this, not ref equal, both Bars, both
    ... equal according to Foo.  Do the Bar logic here.
  }

  // Note that there is no need to override Equals(object). It
  // already has a correct implementation in Foo.

  // And once again we can use the correctness of the previous
  // method to implement the next method.  We need this method
  // to implement IEquatable<Bar>'s contract:

  public bool Equals(Bar b) => this.Equals((object)b);

  // As noted in a comment below, the following are not strictly
  // necessary, as the (Foo, Foo) methods in the base class do
  // the right thing when given two Bars.  However, it might be
  // nice for debugging or self-documenting-code reasons to implement
  // them, and they're easy.  Omit them if you like.

  public static bool Equals(Bar b1, Bar b2) =>
    object.ReferenceEquals(b1, null) ? 
      object.ReferenceEquals(b2, null) : 
      b1.Equals(b2);
  public static bool operator ==(Bar b1, Bar b2) => Equals(b1, b2);
  public static bool operator !=(Bar b1, Bar b2) => !(b1 == b2);
}

我们完成了。我们有 Equals(object)Equals(T)Equals(T, T)==(T, T)!=(T, T) 的样板实现,当您需要这种模式时可以简单地剪切粘贴,并且特定于类型的详细信息进入特定于类型的虚拟保护方法,它们属于那里。