使用static Object.Equals检查null的代码是否比使用==运算符或regular Object.Equals的代码更健壮?后两者是否容易被覆盖,以至于检查null不能按预期工作(例如,当比较值 为null时返回false)?
换句话说,是这样的:
if (Equals(item, null)) { /* Do Something */ }
比这更强大:
if (item == null) { /* Do Something */ }
我个人觉得后面的语法更容易阅读。编写处理作者控制之外的对象的代码(例如库)时是否应该避免?是否应始终避免(检查为空时)?这只是头发分裂吗?
答案 0 :(得分:56)
这个问题没有简单的答案。在我看来,任何一个人总是使用其中一个的建议给你的建议很差。
实际上,您可以调用几种不同的方法来比较对象实例。给定两个对象实例a
和b
,您可以写:
Object.Equals(a,b)
Object.ReferenceEquals(a,b)
a.Equals(b)
a == b
这些都可以做不同的事情!
Object.Equals(a,b)
(默认情况下)将对引用类型执行引用相等性比较,并对值类型执行按位比较。从MSDN文档:
Equals的默认实现 支持引用相等 引用类型和按位相等 对于价值类型。参考平等 表示对象引用 比较指的是同一个对象。 按位相等意味着对象 被比较的具有相同的二进制 表示。
请注意,派生类型可能 将Equals方法重写为 实现价值平等。值 平等意味着比较的对象 具有相同的价值但不同 二进制表示。
请注意上面的最后一段......我们稍后会讨论这个问题。
Object.ReferenceEquals(a,b)
仅执行引用相等性比较。如果传递的类型是盒装值类型,则结果始终为false
。
a.Equals(b)
调用Object
的虚拟实例方法,a
的类型可以覆盖以执行任何操作。调用是使用虚拟调度执行的,因此运行的代码取决于a
的运行时类型。
a == b
调用a
的**编译时类型*的静态重载运算符。如果该运算符的实现在a
或b
上调用实例方法,则它还可能取决于参数的运行时类型。由于调度基于表达式中的类型,因此以下内容可能会产生不同的结果:
Frog aFrog = new Frog();
Frog bFrog = new Frog();
Animal aAnimal = aFrog;
Animal bAnimal = bFrog;
// not necessarily equal...
bool areEqualFrogs = aFrog == bFrog;
bool areEqualAnimals = aAnimal = bAnimal;
所以,是的,使用operator ==
检查空值存在漏洞。实际上,大多数类型不重载==
- 但是从来没有保证。
实例方法Equals()
在这里并不是更好。虽然默认实现执行引用/按位相等性检查,但类型可能会覆盖Equals()
成员方法,在这种情况下将调用此实现。用户提供的实现可以返回它想要的任何内容,即使与null相比也是如此。
但是你要问的Object.Equals()
的静态版本怎么样?这最终会运行用户代码吗?好吧,事实证明答案是肯定的。 Object.Equals(a,b)
的实现扩展到以下内容:
((object)a == (object)b) || (a != null && b != null && a.Equals(b))
您可以自己尝试一下:
class Foo {
public override bool Equals(object obj) { return true; } }
var a = new Foo();
var b = new Foo();
Console.WriteLine( Object.Equals(a,b) ); // outputs "True!"
因此,当调用中的任何类型都不是Object.Equals(a,b)
时,语句null
可以运行用户代码。请注意,Object.Equals(a,b)
不会在任一参数为空时调用Equals()
的实例版本。
简而言之,您获得的比较行为可能会有很大差异,具体取决于您选择调用的方法。但是,这里有一条评论:微软没有正式记录Object.Equals(a,b)
的内部行为。 如果你需要一个铁质的保证,在不运行任何其他代码的情况下将引用与null进行比较,你需要Object.ReferenceEquals()
:
Object.ReferenceEquals(item, null);
此方法使意图极其明确 - 您特别希望结果是两个参考相等的参考相等。使用类似Object.Equals(a,null)
之类的东西的好处是,以后某人不太可能会说:
“嘿,这很尴尬,让我们将其替换为:a.Equals(null)
或a == null
可能有所不同。
让我们在这里注入一些实用主义。到目前为止,我们已经讨论过不同比较模式产生不同结果的可能性。虽然情况肯定如此,但有些类型可以安全地编写a == null
。像String
和Nullable<T>
这样的内置.NET类具有明确定义的语义以供比较。此外,它们是sealed
- 通过继承阻止对其行为的任何更改。以下是非常常见的(也是正确的):
string s = ...
if( s == null ) { ... }
写作是不必要的(而且很丑陋):
if( ReferenceEquals(s,null) ) { ... }
因此,在某些有限的情况下,使用==
是安全且合适的。
答案 1 :(得分:5)
if (Equals(item, null))
并不比if (item == null)
强大,我发现它更难以启动。
答案 2 :(得分:2)
framework guidelines建议您将Equals
视为值相等(检查两个对象是否表示相同的信息,即比较属性),并将==
视为参考相等,不可变对象的异常,您可能应该将==
覆盖为值相等。
因此,假设指南适用于此处,请选择语义上合理的指标。如果您正在处理不可变对象,并且您希望两种方法都能产生相同的结果,那么为了清晰起见,我会使用==
。
答案 3 :(得分:2)
如果您想测试IDENTITY(内存中的相同位置):
ReferenceEquals(a, b)
处理空值。而且不是可以覆盖的。 100%安全。
但请确保您确实需要IDENTITY测试。请考虑以下事项:
ReferenceEquals(new String("abc"), new String("abc"))
返回false
。相反:
Object.Equals(new String("abc"), new String("abc"))
和
(new String("abc")) == (new String("abc"))
都返回true
。
如果您希望在这种情况下得到true
的答案,那么您需要一个EQUALITY测试,而不是IDENTITY测试。
见下一部分。
如果您想测试EQUALITY(相同内容):
如果编译器没有抱怨,请使用“a == b
”。
如果拒绝(如果变量a的类型未定义“==”运算符),则使用“Object.Equals(a, b)
”。
IF 你在 a已知不为空的逻辑内部,那么你可以使用更具可读性的“a.Equals(b)
”。例如,“this.Equals(b)”是安全的。或者,如果“a”是在构造时初始化的字段,并且构造函数抛出异常,如果将null作为要在该字段中使用的值传入。
现在,要解决原来的问题:
问:这些是否容易在某些类中被覆盖,代码无法正确处理null,从而导致异常?
答:是的。获得100%安全EQUALITY测试的唯一方法是自己预先测试零值。
但是你呢?错误就在于(假设的未来坏类),这将是一种直接的失败类型。易于调试和修复(无论谁提供课程)。我怀疑这是一个经常发生的问题,或者在它确实发生时会持续很长时间。
更详细的答案:Object.Equals(a, b)
最有可能面对写得不好的课程。如果“a”为null,则Object类将自己处理它,因此没有风险。如果“b”为空,则DYNAMIC(运行时非编译时)类型“a”确定调用“Equals”方法。当“b”为空时,被调用的方法只需要正常工作。除非被调用的方法编写得非常糟糕,否则它所做的第一步是确定“b”是否是它理解的类型。
所以Object.Equals(a, b)
是可读性/编码工作与安全性之间的合理折衷。
答案 4 :(得分:1)
在参考“...编写将处理作者控制之外的对象的代码......”时,我要指出静态Object.Equals
和==
运算符都是静态方法,因此无法虚拟/覆盖。根据静态类型确定在编译时确定调用哪个实现。换句话说,外部库无法为编译的代码提供不同版本的例程。
答案 5 :(得分:0)
当我尝试比较本身可能为null的对象的唯一ID时,我到了这里。发现更容易先估算缺失的数据,然后进行比较。
Guid currentId = (Object1 == null) ? Guid.Empty : Object1.Id;
Guid newId = (Object2 == null) ? Guid.Empty : Object2.Id;
If (currentId == newId)
{
//do happyface
}
else
{
//do sadface
}