等于(item, null) 或 item == null
使用 static Object.Equals 检查 null 的代码是否更健壮比使用 == 运算符或常规 Object.Equals 的代码?后两者是否容易被覆盖,导致检查 null 无法按预期工作(例如,当比较值为 null 时返回 false)?
换句话说,是这样的:
if (Equals(item, null)) { /* Do Something */ }
比这样更健壮:
if (item == null) { /* Do Something */ }
我个人发现后者的语法更容易阅读。在编写处理作者控制之外的对象(例如库)的代码时是否应该避免这种情况?是否应该始终避免(检查 null 时)?这只是吹毛求疵吗?
Is code that uses the static Object.Equals to check for null more robust than code that uses the == operator or regular Object.Equals? Aren't the latter two vulnerable to being overridden in such a way that checking for null doesn't work as expected (e.g. returning false when the compared value is null)?
In other words, is this:
if (Equals(item, null)) { /* Do Something */ }
more robust than this:
if (item == null) { /* Do Something */ }
I personally find the latter syntax easier to read. Should it be avoided when writing code that will handle objects outside the author's control (e.g. libraries)? Should it always be avoided (when checking for null)? Is this just hair-splitting?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
这个问题没有简单的答案。在我看来,任何说总是使用其中之一的人都给了你不好的建议。
实际上,您可以调用多种不同的方法来比较对象实例。给定两个对象实例
a
和b
,您可以编写:Object.Equals(a,b)
Object.ReferenceEquals(a, b)
a.Equals(b)
a == b
这些都可以做不同的事情!
Object.Equals(a,b)
将(默认情况下)对引用类型执行引用相等比较,对值类型执行按位比较。来自 MSDN 文档:请注意上面的最后一段......我们稍后会讨论这个问题。
Object.ReferenceEquals(a,b)
仅执行引用相等性比较。如果传递的类型是装箱值类型,则结果始终为false
。a.Equals(b)
调用Object
的虚拟实例方法,a
的类型可以重写该方法做任何它想做的事。该调用是使用虚拟调度执行的,因此运行的代码取决于a
的运行时类型。a == b
调用a
的**编译时类型*的静态重载运算符。如果该运算符的实现调用a
或b
上的实例方法,它还可能取决于参数的运行时类型。由于调度是基于表达式中的类型,因此以下可能会产生不同的结果:因此,是的,使用
operator ==
检查空值存在漏洞。实际上,大多数类型不会重载==
- 但永远无法保证。实例方法
Equals()
在这里也好不到哪儿去。虽然默认实现执行引用/按位相等检查,但类型可能会覆盖Equals()
成员方法,在这种情况下将调用此实现。用户提供的实现可以返回它想要的任何内容,即使与 null 进行比较也是如此。但是您会问
Object.Equals()
的静态版本怎么样?这最终可以运行用户代码吗?好吧,事实证明答案是肯定的。Object.Equals(a,b)
的实现扩展为以下内容:您可以自己尝试一下:
因此,语句可能为:
Object.Equals( a,b)
在调用中的任何类型都不为null
时运行用户代码。请注意,当任一参数为 null 时,Object.Equals(a,b)
不会调用Equals()
的实例版本。简而言之,您获得的比较行为类型可能会有很大差异,具体取决于您选择调用的方法。不过,这里有一个评论:微软没有正式记录
Object.Equals(a,b)
的内部行为。 如果您需要在不运行任何其他代码的情况下将引用与 null 进行比较的铁证,那么您需要Object.ReferenceEquals()
:此方法使意图极其清晰 - 您特别期望结果是两个引用的比较以确保引用相等。与使用诸如
Object.Equals(a,null)
之类的东西相比,这里的好处是稍后不太可能有人会说:“嘿,这很尴尬,让我们替换它与:
a.Equals(null)
或a == null
这可能会有所不同,
让我们在这里注入一些实用主义。a == null。 - 在 .NET 类中,例如
String
和Nullable
具有明确定义的比较语义,此外,它们是密封的
- 防止对其进行任何更改。以下是很常见的(也是正确的):没有必要(而且丑陋)编写:
因此在某些有限的情况下,使用
==
是安全且合适的。There's no simple answer for this question. Anyone who says always use one or the other is giving you poor advice, in my opinion.
There are actually several different methods you can call to compare object instances. Given two object instances
a
andb
, you could write:Object.Equals(a,b)
Object.ReferenceEquals(a,b)
a.Equals(b)
a == b
These could all do different things!
Object.Equals(a,b)
will (by default) perform reference equality comparison on reference types and bitwise comparison on value types. From the MSDN documentation:Note the last paragraph above ... we'll discuss this a bit later.
Object.ReferenceEquals(a,b)
performs reference equality comparison only. If the types passed are boxed value types, the result is alwaysfalse
.a.Equals(b)
calls the virtual instance method ofObject
, which the type ofa
could override to do anything it wants. The call is performed using virtual dispatch, so the code that runs depends on the runtime type ofa
.a == b
invokes the static overloaded operator of the **compile-time type* ofa
. If the implementation of that operator invokes instance methods on eithera
orb
, it may also depend on the runtime types of the parameters. Since the dispatch is based on the types in the expression, the following may yield different results:So, yes, there is vulnerability for check for nulls using
operator ==
. In practice, most types do not overload==
- but there's never a guarantee.The instance method
Equals()
is no better here. While the default implementation performs reference/bitwise equality checks, it is possible for a type to override theEquals()
member method, in which case this implementation will be called. A user supplied implementation could return whatever it wants, even when comparing to null.But what about the static version of
Object.Equals()
you ask? Can this end up running user code? Well, it turns out that the answer is YES. The implementation ofObject.Equals(a,b)
expands to something along the lines of:You can try this for yourself:
As a consequence, it's possible for the statement:
Object.Equals(a,b)
to run user code when neither of the types in the call arenull
. Note thatObject.Equals(a,b)
does not call the instance version ofEquals()
when either of the arguments is null.In short, the kind of comparison behavior you get can vary significantly, depending on which method you choose to call. One comment here, however: Microsoft doesn't officially document the internal behavior of
Object.Equals(a,b)
. If you need an iron clad gaurantee of comparing a reference to null without any other code running, you wantObject.ReferenceEquals()
:This method makes the intent extremently clear - you are specifically expecting the result to be the comparison of two references for reference equality. The benefit here over using something like
Object.Equals(a,null)
, is that it's less likely that someone will come along later and say:"Hey, this is awkward, let's replace it with:
a.Equals(null)
ora == null
which potentially may be different.
Let's inject some pragmatism here, however. So far we've talked about the potential for different modalities of comparison to yield different results. While this is certainly the case, there are certain types where it's safe to write
a == null
. Built-in .NET classes likeString
andNullable<T>
have well defined semantics for comparison. Furthermore, they aresealed
- preventing any change to their behavior through inheritance. The following is quite common (and correct):It's unnecessary (and ugly) to write:
So in certain limited cases, using
==
is safe, and appropriate.if (Equals(item, null))
并不比if (item == null)
更强大,而且我发现启动起来更混乱。if (Equals(item, null))
is no more robust thanif (item == null)
, and I find it more confusing to boot.当您想要测试 IDENTITY(内存中的同一位置)时:
处理空值。并且是不可重写的。 100%安全。
但请确保您确实想要身份测试。考虑以下几点:
返回
false
。相比之下:和
都返回
true
。如果您在这种情况下期望得到
true
答案,那么您需要的是 EQUALITY 测试,而不是 IDENTITY 测试。请参阅下一部分。
当你想测试EQUALITY(相同内容)时:
如果编译器没有抱怨,请使用“
a == b
”。Object.Equals(a, b)
”。如果您处于已知a不为空的逻辑内部,那么您可以使用更具可读性的“
a.Equals(b)< /代码>”。例如,“this.Equals(b)”是安全的。或者,如果“a”是在构造时初始化的字段,并且如果传入 null 作为要在该字段中使用的值,则构造函数会抛出异常。
现在,解决最初的问题:
答:是的。获得 100% 安全的 EQUALITY 测试的唯一方法是自己预先测试 null。
但你应该吗?错误就在那个(假设的未来坏类别)中,并且这将是一种简单的失败类型。易于调试和修复(由提供该类的人进行)。我怀疑这是一个经常发生的问题,或者当它发生时仍然持续很长时间的问题。
更详细的答:
Object.Equals(a, b)
最有可能在编写糟糕的类时起作用。如果“a”为 null,则 Object 类将自行处理它,因此没有风险。如果“b”为空,则“a”的动态(运行时而非编译时)类型决定调用什么“Equals”方法。当“b”为空时,被调用的方法只需正确工作即可。除非被调用的方法写得非常糟糕,否则它所做的第一步是确定“b”是否是它理解的类型。因此,
Object.Equals(a, b)
是可读性/编码工作量和安全性之间的合理折衷。When you want to test IDENTITY (same location in memory):
Handles nulls. And is not overridable. 100% safe.
But make sure you really do want IDENTITY test. Consider the following:
which returns
false
. In contrast:and
both return
true
.If you are expecting an answer of
true
in this situation, then you want an EQUALITY test, not an IDENTITY test.See the next part.
When you want to test EQUALITY (same contents):
Use "
a == b
" if the compiler doesn't complain.If that is rejected (if the type of variable a does not define "==" operator), then use "
Object.Equals(a, b)
".IF you are inside of logic where a is known to not be null, THEN you may use the more readable "
a.Equals(b)
". For example, "this.Equals(b)" is safe. Or if "a" is a field that is initialized at construction time, and the constructor throws exception if null is passed in as the value to be used in that field.NOW, to address the original question:
A: Yes. The only way to get 100% safe EQUALITY test would be to pre-test for nulls yourself.
But should you? The bug would be in that (hypothetical future bad class), and it would be a straightforward type of failure. Easy to debug and fix (by whoever supplies the class). I doubt it is a problem that happens often, or persists long when it does happen.
More detailed A:
Object.Equals(a, b)
is most likely to work in the face of a poorly written class. If "a" is null, the Object class will handle it itself, so no risk there. If "b" is null, then the DYNAMIC (run-time not compile-time) type of "a" determines what "Equals" method gets called. The called method merely has to work correctly when "b" is null. Unless the called method is extremely poorly written, the first step it does is determine whether "b" is a type that it understands.So
Object.Equals(a, b)
is a reasonable compromise between readability/coding_effort and safety.框架指南建议您处理
Equals
作为值相等(检查两个对象是否表示相同的信息,即比较属性),以及==
作为引用相等,但不可变对象除外,对于这些对象,您可以使用也许应该重写==
来实现值相等。因此,假设准则适用于此,请选择语义上合理的那个。如果您正在处理不可变对象,并且您希望两种方法产生相同的结果,那么为了清楚起见,我将使用
==
。The framework guidelines suggest that you treat
Equals
as value equality (checking to see whether two objects represent the same information, i.e. comparing properties), and==
as reference equality, with the exception of immutable objects, for which you should probably override==
to be value equality.So, assuming the guidelines apply here, pick whichever is semantically sensible. If you're dealing with immutable objects, and you expect both methods to produce identical results, I'd use
==
for clarity.关于“...编写将处理作者控制之外的对象的代码...”,我会指出 static
Object.Equals
和==
运算符是静态方法,因此不能虚拟/重写。调用哪个实现是在编译时根据静态类型确定的。换句话说,外部库无法为编译后的代码提供不同版本的例程。In reference to "...writing code that will handle objects outside the author's control...", I would point out that both static
Object.Equals
and the==
operator are static methods and therefore cannot be virtual/overridden. Which implementation gets called is determined at compile time based on the static type(s). In other words, there is no way for an external library to provide a different version of the routine to your compiled code.这样做是因为两个空值无法进行比较
Do this as two nulls can not be compared
当我试图比较本身可能为空的对象的唯一 ID 时,我最终来到这里。发现先估算缺失的数据,然后进行比较更容易。
I ended up here when I was trying to compare the unique Id of objects that could themselves be null. Found it easier to just impute the missing data first, and then do the comparison.