等于(item,null)或item == null

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

这个问题没有简单的答案.在我看来,任何说永远使用其中一个的人都会给你不好的建议.

实际上,您可以调用几种不同的方法来比较对象实例.鉴于两个对象实例ab,你可以写:

  • 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)

因此,在某些有限的情况下,使用==是安全的,适当的.

  • 类似的问题:*"我应该总是/永远不做X"*意味着关于所讨论主题的细微差别的知识差距.我觉得这里有一些细节可以帮助澄清为什么我认为简单的答案没有意义. (4认同)
  • 恕我直言,在实践中你应该使用"a == b",除非你知道你想要参考比较.如果类型不支持"=="运算符,则使用Object.Equals(a,b).我建议永远不要使用"a.Equals(b)",因为如果a为null,则会导致异常.如果要实现的类型无法正确处理Object.Equals,那将是该实现中的错误,而不是您使用它的错误.不要去试图避免有人写一个坏课的可能性. (2认同)

Mic*_*urr 5

if (Equals(item, null))并不比健壮if (item == null),并且启动起来更令人困惑。


Too*_*eve 5

当您想测试 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)可读性/编码工作量和安全性之间的合理折衷也是如此。