在一个示例中,我的教授将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
成为必需?
最佳答案
I have no experience with C#, but as far as I know this cannot be null inside a member function (at least this is true in C++ and Java, the languages I know)
首先,请注意您的陈述是错误的。
在C++中,在空接收器上调度方法是未定义的行为,并且未定义的行为意味着任何事情都可能发生。 “任何内容”都包括将
NULL
作为this
传递并继续运行的程序,就好像没有什么错。当然,在C++中检查this
是否为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)
可以为true,但是x==y
可以为false!这太可怕了。请不要去那里。如果要覆盖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
直接与null进行比较即可处理null this
。再说一次:所有这些都说明折衷的立场已经到来,在其中值和引用相等都是可能的,并且即使没有假设
==
也有四种(!=
,object.Equals(object)
,IEquatable<T>.Equals(T)
和this
)实现相等的方法非常复杂且令人困惑。可以或不能为null
。如果您对此主题感兴趣,我将在本周的博客中描述一个稍微棘手的问题:如何实现一般的比较,包括不平等。
http://ericlippert.com/2013/10/07/math-from-scratch-part-six-comparisons/
作为对C#如何处理相等性的批评,这些注释特别有趣。
最后:不要忘记覆盖
GetHashCode
。 Make sure you do it right.
关于c# - 'this'关键字是否可以等于null?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19300245/