在一个示例中,我的教授实现了Equals,如下所示:
public class Person {
private string dni;
// ...
public override bool Equals(object o) {
if (o == null)
return (this == null);
else {
return ((o is Person) && (this.dni == (o as Person).dni));
}
}
}
我没有C#的经验,但是据我所知this
在成员函数中不能为null(至少在C ++和Java(我所知道的语言)中是正确的),因此if似乎对我来说很奇怪。
我是对的还是我不知道c#中的任何组件使测试变得this == null
必要?
我没有使用C#的经验,但是据我所知,在成员函数中这不能为null(至少在C ++和Java(我知道的语言)中是正确的)
首先,请注意您的陈述是错误的。
在C ++中,在空接收器上调度方法是未定义的行为,并且未定义的行为意味着任何事情都可能发生。“任何东西”包括程序传递NULL
的this
和持续的,好像没有什么是错的。当然,this
在C ++中检查是否为null有点愚蠢,因为只有在您不知道程序在做什么的情况下,检查才为true ,因为它的行为是不确定的。
this
在Java中是否可以为null我不知道。
现在来解决您有关C#的问题。假设==
没有重载。稍后我们将回到这一点。
您的方法是用C#编写的。假设使用空接收器从C#程序调用它。C#编译器评估接收方是否可能为null。如果它可能为null,则确保它在调用该方法之前生成对代码进行空检查的代码。因此,在这种情况下,此检查毫无意义。当然,这是99.9999%的可能性。
假设它是通过反射调用的,就像Mike Z的回答一样。在这种情况下,不是C#语言执行调用;而是使用C#语言执行调用。相反,有人故意滥用反射。
假设它是从另一种语言调用的。我们有一个虚拟的方法;如果通过虚拟调度从另一种语言调用它,则必须执行空检查,因为否则我们怎么知道虚拟插槽中的内容呢?在这种情况下,它不能为null。
但是,假设它是使用非虚拟调度从另一种语言调用的。在这种情况下,另一种语言无需实现检查空值的C#功能。它可以调用它并传递null。
因此,在C#中this
可以采用几种方法null
,但是它们都远远超出了主流。因此,人们很少像您的教授那样编写代码。C#程序员习惯上认为this
不是,null
并且从不检查它。
现在我们已经解决了这个问题,让我们再来批评一下该代码。
public override bool Equals(object o) {
if (o == null)
return (this == null);
else {
return ((o is Person) && (this.dni == (o as Person).dni));
}
}
首先,有一个明显的错误。我们假设它this
可以为null,好吧,让我们开始吧。什么会this.dni
引发空引用异常???如果您要假设它this
可以为null,那么至少要始终这样做!(在Coverity,我们将这种情况称为“前向空缺”。)
下一步:我们先重写Equals
然后再使用==
内部,大概是指引用相等。这就是疯狂!现在,我们遇到的情况x.Equals(y)
可能是正确的,但x==y
可能是错误的!这太可怕了。请不要去那里。如果要覆盖,Equals
则同时重载==
,并在执行IEquatable<T>
时实现。
(现在,有一个合理的论点是,疯狂在任何一个方向上都存在;如果与值语义==
一致,Equals
则personx == persony
可以不同于(object)personx == (object)persony
,这似乎也很奇怪。这里的要点是,在C#中,相等性被弄得一团糟。)
而且:如果==
以后重写该怎么办?现在,当代码的作者明确希望进行参考比较时,将Equals
调用重写的==
运算符。这是错误的秘诀。
我的建议是(1)写一个做正确的事情的静态方法,(2)ReferenceEquals
每次可能会混淆什么是相等的含义时使用:
private static bool Equals(Person x, Person y)
{
if (ReferenceEquals(x, y))
return true;
else if (ReferenceEquals(x, null))
return false;
else if (ReferenceEquals(y, null))
return false;
else
return x.dni == y.dni;
}
很好地涵盖了所有情况。请注意,当使用引用相等语义时,对于读者来说是非常清楚的。另请注意,此代码使调试每种可能性都非常容易,可以在每种可能性上设置断点。最后,请注意,我们尽早采用了最便宜的产品。如果对象的引用相等,那么我们就不必对字段进行潜在的昂贵比较!
现在,其他方法很简单:
public static bool operator ==(Person x, Person y)
{
return Equals(x, y);
}
public static bool operator !=(Person x, Person y)
{
return !Equals(x, y);
}
public override bool Equals(object y)
{
return Equals(this, y as Person);
}
public bool Equals(Person y)
{
return Equals(this, y);
}
请注意,我的方式比您教授的方式更加优雅和清晰。并请注意,我的方法this
无需this
直接与null进行比较就可以处理null。
还是那句话:这一切说明,妥协的位置到达,其中价值和参考平等是可能的,有四个(==
,!=
,object.Equals(object)
和IEquatable<T>.Equals(T)
)的方式来实现平等,很复杂,没有假设,甚至混淆this
可以或不可以是null
。
如果您对此主题感兴趣,我将在本周的博客中描述一个稍微棘手的问题:如何实现一般的比较,包括不平等。
http://ericlippert.com/2013/10/07/math-from-scratch-part-six-comparisons/
作为对C#如何处理平等的批评,这些注释特别有趣。
最后:别忘了重写GetHashCode
。确保您做对了。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句