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

时间:2018-08-15 19:59:23

标签: c# oop operators override

我有一个基类(TFoo)和后代类(TBar);我都覆盖了==运算符。我希望后代类检查其自己的字段,并调用基类的==运算符,以便基类将执行其自己的检查。

在下面的代码中,您将看到在TBar == operator中,我强制转换为基类以检查基类是否相等,例如:(TFoo)a == (TFoo)b

(这似乎起作用了!希望我的测试中不会遗漏一些陷阱)。

但是,我正在寻找一种更优雅的方式来做到这一点。例如,(base)a == (base)ba base.== bbase.==(a, b)a.base.Equals(b)等。

显然,以上示例不起作用,可能看起来很荒谬;并且如上所述,(TFoo)a == (TFoo)b确实可以正常工作。我正在寻找一种无需明确命名{em} TFoo类的方法。

编辑:感谢所有精彩的回复!我修改了下面的原始代码,以直接比较.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();
    }
}

3 个答案:

答案 0 :(得分:4)

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

我的建议是:

首先,修复您的实现。它以多种方式被破坏:

  • ==绝不应该崩溃,但是如果为==提供了null操作数,您的实现就会立即崩溃
  • 已经使用值语义对
  • 类型进行了 比较;您绝对不能通过名称来比较两种类型的相等性!您可以从两个具有相同名称的不同程序集中获取两种类型,也可以将来自同一程序集的相同类型的两种类型加载到不同的上下文中!通过比较类型来比较类型。如果要说ab必须是完全相同的类型,请在知道都不为空之后说a.GetType() == b.GetType()

修复了实现后,请对其进行改进:

  • 在可以轻松获得便宜又便宜的东西的情况下。始终首先使用object.ReferenceEquals检查引用是否相等,以使读者清楚这是您在做什么。
  • 相反:如果您要检查引用相等性,请显式调用object.ReferenceEquals,并避免很多愚蠢的错误,当您打算执行引用相等性时会意外地调用operator ==
  • 编写一个一个方法,该方法是给定类型的相等性的单个真理来源,然后从所有其他方法中直接或间接调用该方法实现平等的方法。由于您的意图是使派生类的实现取决于基类的详细信息,因此使其成为受保护的虚拟方法
  • 由于您无论如何都要进行所有这些工作,因此您不妨在自己的类型上实现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 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)的样板实现,当您需要此模式时,可以简单地对其进行剪切和粘贴。特定于类型的详细信息将进入它们所属的特定于类型的虚拟保护方法。

答案 1 :(得分:0)

我认为您可以简单地在Equals中覆盖TFoo并从==中调用它,然后在TBar中再次覆盖它。然后,您只需从base.Equals调用TBar.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
}

答案 2 :(得分:0)

必须强制转换(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 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();
        }
    }
}