C#如何调用基类==运算符,而不显式地进行类型转换?
我有一个基类(TFoo)和后代类(TBar);我正在重写这两个的==运算符。我希望子类检查自己的字段,并调用基类的==运算符,以便基类执行自己的检查 在下面的代码中,您将看到在C#如何调用基类==运算符,而不显式地进行类型转换?,c#,oop,operators,overriding,C#,Oop,Operators,Overriding,我有一个基类(TFoo)和后代类(TBar);我正在重写这两个的==运算符。我希望子类检查自己的字段,并调用基类的==运算符,以便基类执行自己的检查 在下面的代码中,您将看到在TBar==运算符中,我向基类键入cast以检查基类的相等性,如下所示:(TFoo)a==(TFoo)b (这似乎是可行的!希望我在测试中没有遗漏任何一点。) 然而,我正在寻找一种更优雅的方式来做到这一点。例如,(base)a==(base)b或a base.==b或base.==(a,b)或a.base.Equals(b
TBar==运算符中,我向基类键入cast以检查基类的相等性,如下所示:(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
。
我认为您可以简单地在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
中重写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)
是相同的ab.Equals(a)
,因为如果a
和b
有不同的类型,那么将调用Equals
的不同实现。因此,我建议通过调用a.Equals(b)和&b.Equals(a)
(并处理空值)来实现=
操作符
以及派生类
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();
}
}
}
必须执行强制转换(TFoo)a==(TFoo)b
的原因是=
运算符是静态的。也就是说,不考虑运行时类型,而是使用编译时已知的静态类型的=
运算符(如果不“强制转换”)。相反,Equals
是一个实例成员,其实现在运行时由调用方对象的实际类型决定。如果需要动态行为,请将==
操作符基于Equals
我们希望==
操作符是对称的。也就是说,a==b
应该与b==a
相同。但是我们不一定期望a.Equals(b)
是相同的ab.Equals(a)
,因为如果a
和b
有不同的类型,那么将调用Equals
的不同实现。因此,我建议通过调用a.Equals(b)和&b.Equals(a)
(并处理空值)来实现=
操作符
以及派生类
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
操作数,则实现会立即崩溃
- 类型已经使用值语义进行了比较;你永远不应该比较两种类型的名称是否相等!您可以从两个具有相同名称的不同程序集中加载两种类型,或者从同一程序集中加载两个具有相同类型的类型到不同的上下文中!通过比较类型来比较类型。如果意图是说
a
和b
必须是完全相同的类型,那么在知道两者都不为空后,说a.GetType()==b.GetType()
一旦修复了实现,请改进它:
- 当你能得到它们的时候,就去买便宜、容易的东西。始终首先使用
object.ReferenceEquals
检查引用相等性,以向读者明确这就是您正在做的事情
- 相反:如果要检查引用相等,请显式调用
object.ReferenceEquals
,避免在执行引用相等时意外调用operator==
时出现许多愚蠢的错误
- 为给定类型编写一个方法,该方法是等式的唯一来源,然后直接或间接地从实现等式的所有其他方法调用该方法。由于您的意图是使派生类的实现取决于基类的详细信息,将其作为受保护的虚拟方法
- 既然您正在以任何方式完成所有这些工作,那么您最好在您的类型上实现
IEquatable
我会这样做:
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);
...
}
Fo类
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);
}