Jos*_* D. 30 .net c# oop equals
在一个例子中,我的教授已经实现了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));
}
}
}
Run Code Online (Sandbox Code Playgroud)
我没有使用C#的经验,但据我所知,this
在成员函数中不能为null(至少在C++和Java中,我知道的语言是这样)所以if看起来很奇怪.
我是对的还是c#中有任何组件我不知道哪个使测试成为this == null
必要的?
Eri*_*ert 58
我没有使用C#的经验,但据我所知,这在成员函数中不能为null(至少在C++和Java中是这样,我知道的语言)
让我们首先注意你的陈述是错误的.
在C++中,在null接收器上调度方法是未定义的行为,未定义的行为意味着任何事情都可能发生."任何东西"包括程序传递NULL
的this
和持续的,好像没有什么是错的.当然,this
在C++中检查是否为null 是有点愚蠢的,因为如果你已经不知道你的程序在做什么,那么检查只能是真的,因为它的行为是未定义的.
this
Java中是否可以为null我不知道.
现在来解决你关于C#的问题.我们假设==
没有超载.我们稍后会回到这一点.
您的方法是用C#编写的.假设它是从带有空接收器的C#程序调用的.C#编译器评估接收者是否可能为null; 如果它可能为null,那么它确保它生成在调用方法之前执行空检查的代码.因此,在这种情况下,这种检查毫无意义.这当然是99.9999%可能的情况.
假设它是通过Reflection调用的,就像在mike z的答案中那样.在这种情况下,执行调用的不是C#语言; 相反,有人故意滥用反思.
假设它是从另一种语言调用的.我们有一个虚拟方法; 如果使用虚拟调度从其他语言调用它,则必须执行空检查,因为我们怎么知道虚拟插槽中的内容?在那种情况下,它不能为空.
但是假设它是使用非虚拟调度从另一种语言调用的.在这种情况下,另一种语言不需要实现检查null的C#功能.它可以只调用它并传递null.
因此,有几种方法this
可以null
在C#中使用,但它们都远离主流.因此,人们很少像您的教授那样编写代码.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));
}
}
Run Code Online (Sandbox Code Playgroud)
首先,有一个明显的错误.我们假设this
可能是null,好吧,让我们运行它. 什么停止this.dni
抛出null引用异常??? 如果你假设this
可以为null,那么至少要这样做!(在Coverity,我们将这种情况称为"前向无效缺陷".)
下一步:我们重写Equals
然后==
在内部使用,大概是指参考平等.这种方式就是疯狂!现在我们的情况x.Equals(y)
可能是真的,但x==y
可能是假的!这太可怕了.请不要去那里.如果你要覆盖,Equals
那么同时重载==
,并IEquatable<T>
在你执行时实现.
(现在,有一个合理的论据可以说,疯狂在于任何一个方向;如果与价值语义==
一致,Equals
那么personx == persony
可能会有所不同(object)personx == (object)persony
,这似乎也很奇怪.这里要说的是,在C#中,相等性是相当混乱的.)
而且:如果==
以后被覆盖怎么办?现在Equals
正在调用一个重写的==
运算符,当代码的作者明确希望进行参考比较时.这是bug的秘诀.
我的建议是(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;
}
Run Code Online (Sandbox Code Playgroud)
这很好地涵盖了每一个案例.请注意,当引用相等语义时,读者可以清楚地看到它.另请注意,为了进行调试,此代码可以很容易地为每种可能性设置断点.最后,请注意我们尽早选择最便宜的; 如果对象的引用相等,那么我们就不必对字段进行潜在的昂贵比较!
现在其他方法很简单:
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);
}
Run Code Online (Sandbox Code Playgroud)
请注意我的方式比你教授的方式更优雅和清晰.并注意我的方式处理null this
而不this
直接比较null.
还是那句话:这一切说明,妥协的位置到达,其中价值和参考平等是可能的,有四个(==
,!=
,object.Equals(object)
和IEquatable<T>.Equals(T)
)的方式来实现平等,是非常复杂和混乱,即使没有假设this
能或不能null
.
如果这个主题让你感兴趣,我在本周的博客中描述了一个稍微困难的问题:如何实现一般的比较,包括不平等.
http://ericlippert.com/2013/10/07/math-from-scratch-part-six-comparisons/
作为对C#如何处理平等的批评,这些评论特别有趣.
最后:别忘了覆盖GetHashCode
. 确保你做得对.
Mik*_*ray 13
是的,在某些情况下.最常见的情况是,如果使用由反射创建的委托调用实例方法并传递空接收器.这将打印"True":
public class Test
{
public void Method()
{
Console.WriteLine(this == null);
}
}
public class Program
{
public static void Main(string[] args)
{
object t = new Test();
var methodInfo = t.GetType().GetMethod("Method");
var a = (Action)Delegate.CreateDelegate(typeof(Action), null, methodInfo);
a();
}
}
Run Code Online (Sandbox Code Playgroud)
老实说,虽然我从未见过有人this
在生产代码中检查过null.