在一个例子中,我的教授实施了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
成为必要的?
答案 0 :(得分:58)
我没有C#的经验,但据我所知,在成员函数中不能为null(至少在C ++和Java中,我知道的语言是这样)
首先,请注意您的陈述是错误的。
在C ++中,在空接收器上调度方法是未定义的行为和未定义的行为意味着任何都可能发生。 "任何"包括将NULL
作为this
传递的程序,并继续,好像没有任何错误。当然,在C ++中检查this
是否为空是有些愚蠢的,因为如果你已经不知道你的程序正在做什么,那么检查只能是真的,因为它的行为是未定义的。
Java中this
是否为null我不知道。
现在解决有关C#的问题。我们假设==
没有超载。我们稍后会回到这一点。
您的方法是用C#编写的。假设它是从带有空接收器的C#程序调用的。 C#编译器评估接收者是否可能为null;如果它可能是null,那么它确保它生成在调用方法之前执行空检查的代码。因此,在这种情况下,这种检查毫无意义。这当然是99.9999%可能的情况。
假设它是通过Reflection调用的,就像在mike z的回答中一样。在这种情况下,执行调用的不是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
可能与{==
不同1}},这看起来也很奇怪。这里要说的是C#中的相等性很混乱。)
此外:如果Equals
稍后被覆盖怎么办?现在==
正在调用一个被覆盖的ReferenceEquals
运算符,当代码的作者明确希望进行参考比较时。这是一个错误的秘诀。
我的建议是(1)编写一个做正确事情的静态方法,(2)每次可能存在任何混淆意味着什么样的平等意味着使用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进行比较。
再次说明:所有这些都说明了妥协的立场,其中价值和参考平等都是可能的,有四个(!=
,object.Equals(object)
,IEquatable<T>.Equals(T)
和{{ 1}})实现平等的方法,即使不假设this
能够或不能null
,也是非常复杂和令人困惑的。
如果这个主题让你感兴趣,我在本周的博客中描述了一个稍微困难的问题:如何实施一般的比较,包括不平等。
http://ericlippert.com/2013/10/07/math-from-scratch-part-six-comparisons/
这些评论作为对C#如何处理平等的批评特别有趣。
最后:不要忘记覆盖GetHashCode
。 Make sure you do it right.
答案 1 :(得分: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();
}
}
老实说,虽然我从未见过有人在生产代码中检查this
为空。
答案 2 :(得分:2)
如果您通过重载this
运算符来练习一些黑魔法,==
可以null
==
的一种方式。例如,如果某人决定使用null或空字符串dni
等同于空人:
public static bool operator ==(Person a, Person b)
{
if(String.IsNullOrEmpty(a.dni) && (object)b == null)
return true;
if(String.IsNullOrEmpty(b.dni) && (object)a == null)
return true;
return Object.ReferenceEquals(a, b);
}
请注意,这可能是一个非常糟糕的主意。
答案 3 :(得分:2)
是的,这是可能的,事实上并非完全不可能。 C#语言提供了一个很好的保证,即不能在对类对象的空引用上调用任何方法,它会生成在调用站点进行空检查的代码。这肯定会避免很多麻烦,如果没有检查,方法内部的NullReferenceException很难调试。
然而,该检查特定于C#,并非所有.NET语言都实现它。例如,C ++ / CLI语言不是。您可以通过使用C ++ CLR控制台模式项目和C#类库创建解决方案来自行查看。将CLR项目中的引用添加到C#项目中。
C#代码:
using System;
namespace ClassLibrary1 {
public class Class1 {
public void NullTest() {
if (this == null) Console.WriteLine("It's null");
}
}
}
C ++ / CLI代码:
#include "stdafx.h"
using namespace System;
int main(array<System::String ^> ^args) {
ClassLibrary1::Class1^ obj = nullptr;
obj->NullTest();
Console::ReadLine();
return 0;
}
输出:
它是空的
这不是其他未定义的行为或以任何方式违法。 CLI规范不是 verboten 。只要该方法不访问任何实例成员,那么什么都不会出错。 CLR也处理这个问题,还为非空指针生成NullReferenceException。就像NullTest()访问一个不是该类中第一个字段的字段一样。