换句话说,这样做是否更好:
if (Equals(item, null)) { /* Do Something */ }
比这个更强大:
if (item == null) { /* Do Something */ }
我个人觉得后一种语法更容易阅读。在编写处理作者无法控制的对象(例如库)的代码时,应该避免使用后一种语法吗?在检查是否为空时,它应该始终被避免吗?这只是纠结吗?
if (Equals(item, null)) { /* Do Something */ }
if (item == null) { /* Do Something */ }
这个问题没有简单的答案。 在我看来,任何人都建议您总是使用其中一个方法而不是另一个方法都是错误的建议。
实际上,您可以调用几种不同的方法来比较对象实例。给定两个对象实例a
和b
,您可以编写以下代码:
Object.Equals(a,b)
Object.ReferenceEquals(a,b)
a.Equals(b)
a == b
这些方法可能会执行不同的操作!
Object.Equals(a,b)
(默认情况下)将在引用类型上执行引用相等性比较,在值类型上执行按位比较。从MSDN文档中可以看到:
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
,也有可能运行用户代码。请注意,当任一参数为 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
"
这可能会导致不同的结果。
然而,让我们注入一些实用主义。到目前为止,我们已经谈论了不同比较方式可能产生不同结果的潜力。虽然这当然是正确的,但是对于某些类型,像String
和Nullable<T>
这样的内置.NET类具有定义良好的比较语义。此外,它们是sealed
- 通过继承防止其行为发生任何变化。以下代码非常常见(也是正确的):
string s = ...
if( s == null ) { ... }
写下以下代码是不必要的(也很丑陋):
if( ReferenceEquals(s,null) ) { ... }
因此,在某些有限的情况下,使用==
是安全和合适的。
if (Equals(item, null))
不比if (item == null)
更加健壮,而且我觉得它更加令人困惑。
当您想测试IDENTITY(在内存中相同的位置)时:
ReferenceEquals(a, b)
处理空值。不可重写。百分之百安全。
但请确保您真的想要进行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)
"。
如果您在已知a不为null的逻辑内部,则可以使用更具可读性的"a.Equals(b)
"。例如,"this.Equals(b)"是安全的。或者如果"a"是在构造时初始化的字段,并且构造函数在传递null作为该字段要使用的值时引发异常。
现在,针对原始问题:
Q: 这些是否容易被某个类覆盖,其中代码不能正确处理null,从而导致异常?
A:是的。唯一获得百分之百安全EQUALITY测试的方法是自己预先测试null。
但是您应该吗?错误将出现在(假想的未来不良类)中,并且它将是一种直接类型的故障。由谁提供类来调试和修复很容易。我怀疑这经常发生或长时间存在。
更详细的答案:Object.Equals(a, b)
最有可能在面对编写不良的类时正常工作。如果"a"为null,则Object类本身将处理它,因此没有风险。如果"b"为null,则运行时而不是编译时的"a"的动态类型确定了何时调用"Equals"方法。被调用的方法只需在"b"为null时正常工作即可。除非所调用的方法编写得极其不良,否则它的第一步将确定"b"是否为它所理解的类型。
Object.Equals(a, b)
是可读性、编码难度和安全性之间的合理折衷。框架指南 建议您将 Equals
视为值相等性(检查两个对象是否表示相同的信息,即比较属性),并将 ==
视为引用相等性,除了不可变对象以外,对于这些对象,您应该将 ==
重写为值相等性。
所以,假设这里适用于指南,请选择语义上合理的方法。如果您正在处理不可变对象,并且期望两种方法产生相同的结果,我建议使用 ==
以获得清晰度。
Object.Equals
和==
运算符都是静态方法,因此不能被虚拟/重写。哪个实现被调用是在编译时基于静态类型(s)确定的。换句话说,外部库无法为您编译后的代码提供不同版本的例程。object.Equals(a,b)
也是如此,它执行类似于 ((object)a == (object)b) || (a != null && b != null && a.Equals(b))
的操作,也就是说,它可能会调用对象 a
上的虚拟 Equals
方法。 - LukeHEquals(Object)
方法,静态的Object.Equals(Object,Object)
方法将会在其中一个对象不为空时调用该重写方法;无论其参数如何转换类型都可以工作。相比之下,即使一个类型重载了==
运算符,它也不能保证在转换为Object
时使用该重载。 - supercat(a == null ? "NA" : a).equals(b == null ? "NA" : b)
我在尝试比较可能为空的对象的唯一标识符时,最终来到了这里。发现先填充缺失数据,然后再进行比较更容易。
Guid currentId = (Object1 == null) ? Guid.Empty : Object1.Id;
Guid newId = (Object2 == null) ? Guid.Empty : Object2.Id;
If (currentId == newId)
{
//do happyface
}
else
{
//do sadface
}
is
运算符:if (item is null)
,从C# 7+版本开始可用。请参阅:“x is null”和“x == null”之间有什么区别? - undefined