我有一个基类(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
确实可以正常工作。我正在寻找一种无需明确命名{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();
}
}
答案 0 :(得分:4)
在C#中正确而优雅地实现平等是不必要的;我坚信这是该语言设计不足的领域。
我的建议是:
首先,修复您的实现。它以多种方式被破坏:
==
绝不应该崩溃,但是如果为==
提供了null
操作数,您的实现就会立即崩溃a
和b
必须是完全相同的类型,请在知道都不为空之后说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)
相同,因为如果a
和b
具有不同的类型,那么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();
}
}
}