【问题标题】:C# How to call the base class == operator, without explicitly typecasting?C# 如何调用基类 == 运算符,而不显式类型转换?
【发布时间】:2018-08-15 19:59:22
【问题描述】:

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

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

(这似乎有效!希望我在测试中没有遗漏一些问题。)

但是,我正在寻找一种更优雅的方式来执行此操作。比如(base)a == (base)b或者a base.== b或者base.==(a, b)或者a.base.Equals(b)或者什么的。

显然,上面的例子不起作用,可能看起来很荒谬;如前所述,(TFoo)a == (TFoo)b 确实可以正常工作。我正在寻找一种方法来做到这一点无需明确命名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();
    }
}

【问题讨论】:

  • 你也可以只写a.GetType() == b.GetType()而不写.Name(obj as TFoo) == this
  • a == b as TFoo 呢?
  • 我不明白您为什么要检查两种类型的反映名称是否相等。你为什么要做这种奇怪的、危险的、错误的事情?如果意图是说类型必须相同,那么为什么不直接比较类型
  • 另外,这很危险,因为它不是空安全的。我们预计 foo == null 应该可以工作,但在您的实现中,它总是崩溃。

标签: c# oop operators overriding


【解决方案1】:

在 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) 的样板实现,当您需要这种模式时,可以简单地剪切-粘贴,特定类型的详细信息进入类型-特定的虚拟保护方法,它们所属的位置。

【讨论】:

  • 如果你最终调用的是虚拟的EqualsImplementation方法,并且继承了二元运算符,是否需要在派生类中实现==运算符,前提是你已经重载了EqualsImplementation ?
  • @JonathonChase:这是一个很棒的观察!我认为实现它们对于调试目的和自我记录目的都很好——它向消费者发送一条消息,即比较两个 Bars 是否相等是设计使然——但你注意到这并不是绝对必要的,这是正确的.我将在代码中添加注释。
  • 我在玩这个时犯的一个错误是没有确保每个可能的路径都在检查操作数之间的类型相等性。在某些时候,您已经涵盖了通过 Foo.Equals(Object) 方法的所有内容,但未能涵盖这种情况肯定会导致像 (SomeFoo == SomeBar) != (SomeBar == SomeFoo) 这样的奇怪事情是真的。我想这只是领土问题,但这是一个潜在的严重错误。
  • @JonathonChase:是的,这整个区域不过是陷阱。这就是为什么我认为这是一个设计失败。我们希望 C# 成为“成功之坑”的语言,正确的方法和简单的方法是一样的。平等绝对没有那个属性!这个问题有很多根源,但一个很大的问题是相等性被定义为单个调度虚拟方法一个静态调度方法,而我们想要的是一个双重调度虚拟方法它作为你注意必须保持对称。运行时中根本不存在这个概念。
  • “你可以从两个不同的程序集中拥有两个同名的类型”——Delphi 的“'TBitmap' 实例不是 'TBitmap' 类的实例”消息的立即闪回。
【解决方案2】:

我认为您可以简单地在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
}

【讨论】:

    【解决方案3】:

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

    我们希望== 运算符是对称的。即,a == b 应该与 b == a 相同。但我们不一定期望a.Equals(b)b.Equals(a) 相同,因为如果ab 具有不同的类型,那么Equals 的不同实现将被调用。因此,我建议通过调用a.Equals(b) &amp;&amp; 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();
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2015-06-29
      • 2017-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-29
      • 1970-01-01
      相关资源
      最近更新 更多