Jos*_*ant 39 c# null equals robustness
使用静态Object.Equals检查null的代码是否比使用==运算符或常规Object.Equals的代码更健壮?是不是后两种易受这样一种方式,检查空预期不工作正在被超越(如返回false时,比较值是零)?
换句话说,是这样的:
if (Equals(item, null)) { /* Do Something */ }
Run Code Online (Sandbox Code Playgroud)
比这更强大:
if (item == null) { /* Do Something */ }
Run Code Online (Sandbox Code Playgroud)
我个人觉得后面的语法更容易阅读.编写处理作者控件之外的对象的代码(例如库)时应该避免吗?是否应始终避免(检查为空时)?这只是头发分裂吗?
LBu*_*kin 63
这个问题没有简单的答案.在我看来,任何说永远使用其中一个的人都会给你不好的建议.
实际上,您可以调用几种不同的方法来比较对象实例.鉴于两个对象实例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;
Run Code Online (Sandbox Code Playgroud)
所以,是的,使用时检查空值存在漏洞operator ==
.在实践中,大多数类型不会过载==
- 但从来没有保证.
实例方法Equals()
在这里并不好.虽然默认实现执行引用/按位相等性检查,但类型可能会覆盖Equals()
成员方法,在这种情况下将调用此实现.用户提供的实现可以返回它想要的任何内容,即使与null相比也是如此.
但是Object.Equals()
你问的静态版本怎么样?这最终会运行用户代码吗?好吧,事实证明答案是肯定的.实施Object.Equals(a,b)
扩展到以下方面:
((object)a == (object)b) || (a != null && b != null && a.Equals(b))
Run Code Online (Sandbox Code Playgroud)
你可以自己试试:
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!"
Run Code Online (Sandbox Code Playgroud)
因此,语句可能是:Object.Equals(a,b)
当调用中的两种类型都不运行时运行用户代码null
.请注意,当任一参数为null时,Object.Equals(a,b)
不会调用实例版本Equals()
.
简而言之,您获得的比较行为可能会有很大差异,具体取决于您选择调用的方法.然而,这里有一条评论:微软没有正式记录其内部行为Object.Equals(a,b)
.如果你需要一个没有任何其他代码运行的比较引用null的铁质保证,你需要Object.ReferenceEquals()
:
Object.ReferenceEquals(item, null);
Run Code Online (Sandbox Code Playgroud)
这种方法使得意图极其明确 - 您特别期望结果是两个参考相等的参考相等.这里使用类似的东西的好处Object.Equals(a,null)
是,以后某人不太可能会说:
"嘿,这很尴尬,让我们替换它:a.Equals(null)
或者a == null
可能有所不同.
但是,让我们在这里注入一些实用主义.到目前为止,我们已经讨论了不同比较模式产生不同结果的可能性.虽然情况肯定如此,但某些类型的编写是安全的a == null
.内置.NET类,String
并且Nullable<T>
具有明确定义的语义以供比较.此外,他们sealed
- 通过继承防止他们的行为发生任何变化.以下是很常见的(也是正确的):
string s = ...
if( s == null ) { ... }
Run Code Online (Sandbox Code Playgroud)
这是不必要的(和丑陋的)写:
if( ReferenceEquals(s,null) ) { ... }
Run Code Online (Sandbox Code Playgroud)
因此,在某些有限的情况下,使用==
是安全的,适当的.
当您想测试 IDENTITY(内存中的同一位置)时:
ReferenceEquals(a, b)
处理空值。并且是不可重写的。100%安全。
但请确保您确实想要身份测试。考虑以下:
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 作为要在该字段中使用的值,则构造函数会引发异常。
现在,解决原来的问题:
问:这些是否容易在某些类中被覆盖,代码不能正确处理 null,从而导致异常?
答:是的。获得 100% 安全的 EQUALITY 测试的唯一方法是自己预先测试 null。
但你应该吗?错误就在那个(假设的未来坏类别)中,并且这将是一种简单的失败类型。易于调试和修复(由提供该类的人进行)。我怀疑这是一个经常发生的问题,或者当它发生时仍然持续很长时间的问题。
更详细的A:Object.Equals(a, b)
面对写得不好的类最有可能起作用。如果“a”为 null,则 Object 类将自行处理它,因此没有风险。如果“b”为空,则“a”的动态(运行时而非编译时)类型决定调用什么“Equals”方法。当“b”为空时,被调用的方法只需正确工作即可。除非被调用的方法写得非常糟糕,否则它所做的第一步是确定“b”是否是它理解的类型。
因此,Object.Equals(a, b)
可读性/编码工作量和安全性之间的合理折衷也是如此。