为什么在C#中经常看到"null != variable"而不是"variable != null"?

104

在C#中,条件语句的书写顺序是否会影响执行速度?

if (null != variable) ...
if (variable != null) ...

最近我经常看到第一种,因为我习惯了第二种,所以引起了我的注意。

如果没有区别,第一种有什么优势呢?


3
好的,我会尽力进行翻译。以下是所需的翻译内容:Yoda条件是一种编程约定,旨在防止由于单个等号(=)而导致的赋值错误。它要求将常量或文字字符串放在等号的左边,并将变量放在右边,以便如果意外地使用了一个等号而不是两个等号(==),则编译器将拒绝编译代码。这种约定是以星球大战角色尤达命名的,因为他经常使用不寻常的语法结构。 - Ivan Chau
一些编程语言支持在if条件中初始化值。在这种语言中,如果我们想要比较LValue和RValue,最好的做法是将字面量放在左侧,例如if(1==i)。因此,如果我们不小心写成=而不是==,那么就会产生编译器错误。 - Jugal Panchal
9个回答

170

这是C语言的一种延续。在C语言中,如果你使用了一个糟糕的编译器或者没有将警告设置足够高,那么这段代码将完全没有任何警告就可以编译通过(并且确实是合法的代码):

// Probably wrong
if (x = 5)

你实际上可能想表达的是

if (x == 5)

在C语言中,您可以通过以下方式解决此问题:

if (5 == x)

一个错别字会导致无效的代码。

现在,在C#中这都是小意思。除非你要比较两个布尔值(这很少见),否则你可以编写更易读的代码,因为“if”语句需要以布尔表达式开头,而“x=5”的类型是Int32而不是Boolean

我建议,如果你在同事的代码中看到这样的写法,你应该教育他们了解现代语言的方式,并建议他们今后写更自然的形式。


7
我的同事让我感到非常烦恼,即使在if之后只有一个语句,他们也想要加上花括号(以防你“不知不觉”地添加了其他内容)。希望F#、 Boo(和YAML)早日问世,如果你的编程语言没有缩进就难以阅读,那么就将其作为语言的一部分吧。请记住,在if语句中加入花括号的主要目的是使代码更加清晰易懂,而不是为了纯粹的风格问题。 - Benjol
52
在我看来,添加括号是合理的——C#并没有试图阻止它成为一个问题。这与“常量==变量”问题非常不同。 - Jon Skeet
6
如果 (this != that) { performAsSuch(); } 实际上是相当健康的。关于“后期添加”的论点在我看来就像不避免/发现 if (a=5) 打字错误一样脆弱。 - jpinto3912
3
@jpinto: 我不太确定你在这里试图表达什么意思 - 你是说你喜欢在单个语句周围加上大括号,还是不喜欢?与问题中变量的情况不同的是,在C#中,有错别字的代码是无效的代码,因此编译器会停止它。对于不添加大括号的情况则不是如此。 - Jon Skeet
4
@Benjol 不要忘记始终提供一个空的 else { } 子句,以防有人需要稍后添加内容但忘记写入 else 关键字。 (玩笑话) - Jeppe Stig Nielsen

14

就像大家已经注意到的那样,它更多或少来自于C语言,在那里,如果您不小心忘记第二个等号,可能会得到错误的代码。但是还有另一个原因与C#相匹配:可读性。

只需拿这个简单的例子:

if(someVariableThatShouldBeChecked != null
   && anotherOne != null
   && justAnotherCheckThatIsNeededForTestingNullity != null
   && allTheseChecksAreReallyBoring != null
   && thereSeemsToBeADesignFlawIfSoManyChecksAreNeeded != null)
{
    // ToDo: Everything is checked, do something...
}
如果你把所有的null单词移到开头,你就可以更容易地找到所有检查。
if(null != someVariableThatShouldBeChecked
   && null != anotherOne
   && null != justAnotherCheckThatIsNeededForTestingNullity
   && null != allTheseChecksAreReallyBoring
   && null != thereSeemsToBeADesignFlawIfSoManyChecksAreNeeded)
{
    // ToDo: Everything is checked, do something...
}

所以这个例子可能是一个不好的例子(参考编码准则),但只需想想你快速滚动整个代码文件时所看到的模式。

if(null ...

你立即知道接下来会发生什么。

如果情况反过来,你总是不得不扫描到行末才能看到空值检查,让你需要几秒钟去找出那里进行了什么样的检查。因此,也许语法高亮可以帮助你,但当这些关键字在行末而非行首时,你总是会更慢。


12

使用null的好处在于: if(null == myDuck)

如果你的Duck类重写了==运算符,则if(myDuck == null)可能会进入无限循环。

使用null首先使用默认的相等比较器,并实际执行了你想要的操作。

(我听说你最终会习惯阅读写成这种方式的代码 - 只是我还没有经历那种变化)。

以下是一个例子:

public class myDuck
{
    public int quacks;
    static override bool operator ==(myDuck a, myDuck b)
    {
        // these will overflow the stack - because the a==null reenters this function from the top again
        if (a == null && b == null)
            return true;
        if (a == null || b == null)
            return false;

        // these wont loop
        if (null == a && null == b)
            return true;
        if (null == a || null == b)
            return false;
        return a.quacks == b.quacks; // this goes to the integer comparison
    }
}

13
这是错误的。已经被 downvote。只需将鼠标指向 == 运算符并停留在那里,您就会看到在两种情况下都使用了用户定义的重载。当然,如果您先检查 a 再检查 b,然后将 a 的位置从右操作数交换到左操作数可能会有微妙的差异。但是您也可以在检查 b 之前检查 a,然后 b == null 就成了反转顺序的变量。这样让运算符自己调用会很混乱。相反,将任一操作数(或两个操作数)强制转换为 object,以获取所需的重载效果。例如 if ((object)a == null)if (a == (object)null) - Jeppe Stig Nielsen
晚上开派对;如果那样都能行的话;这是一种令人讨厌的代码气味,因为你要求一个类重载其 == 运算符,但是这是任意的! - Russ Clarke

10

我猜这是一位转换语言的C程序员。

在C中,你可以写出以下代码:

int i = 0;
if (i = 1)
{
    ...
}

注意这里使用了单等号,这意味着代码将把1赋给变量i,然后返回1(赋值是一个表达式),并在if语句中使用1,这将被处理为true。换句话说,上面的代码是一个错误。
然而,在C#中,这是不可能的。两者之间确实没有区别。

1
然而,在C#中,这是不可能的,因为if语句需要布尔表达式,而提供的表达式是int类型。 - Robert Harvey

5

在早期,人们会忘记 '!'(或者更难发现的相等性额外 '='),而执行赋值操作而不是比较操作。将 null 放在前面可以消除这种错误的可能性,因为 null 不是一个 l-value(即它不能被赋值)。

现代大多数编译器在条件语句中执行赋值操作时会发出警告,而 C# 实际上会给出错误提示。由于一些人认为 var == null 方案更易读,所以大多数人都坚持使用这种方案。


所有的C#编译器(请注意这是一个C#问题)都应该在表达式不是布尔类型时无法编译“if”语句... - Jon Skeet

4

我不认为遵循这个惯例有任何优势。在 C 语言中,布尔类型不存在,因此写成以下形式是有用的:

if( 5 == variable)

相比于
if (variable == 5)

因为如果你忘记了其中一个等号,你最终会得到

if (variable = 5)

在Python中,一个变量可以赋值为5,并且总是计算为true。但在Java中,布尔类型是布尔类型。使用!=时,没有任何理由。

然而,一个好的建议是编写:

if (CONSTANT.equals(myString))

而不是

if (myString.equals(CONSTANT))

因为它有助于避免NullPointerException。 我的建议是要求对该规则进行解释。 如果没有解释,为什么要遵循它? 这不会提高可读性。

0

对我来说,这总是取决于你喜欢哪种风格。

@Shy - 另一方面,如果您混淆了运算符,则应该希望出现编译错误,否则您将运行具有错误的代码 - 这种错误会在以后产生意外行为时回来咬你一口。


我认为我从未听说过有人喜欢“constant == variable”形式,除了出于安全原因之外,在C#中已经处理了这些原因。 - Jon Skeet
我个人更喜欢使用 "variable == constant" 的方式,但我也见过一些程序员采用相反的方式,因为他们觉得这样读起来更自然。 - TheCodeJunkie
1
他们都是被C语言洗脑多年的人,还是真正做出了选择? - Jon Skeet

0
正如许多人指出的那样,它主要用于旧的 C 代码中,以标识编译错误,因为编译器可以将其视为合法代码。
新的编程语言如 Java 和 Go 聪明到足以捕获此类编译错误。
在代码中不应使用类似 "null != variable" 的条件,因为它非常难以阅读。

-4

还有一件事...如果你要比较一个变量和一个常量(例如整数或字符串),把常量放在左边是一个好习惯,因为你永远不会遇到NullPointerException:

int i;
if(i==1){        // Exception raised: i is not initialized. (C/C++)
  doThis();
}

int i;
if(1==i){        // OK, but the condition is not met.
  doThis();
}

现在,由于默认情况下C#实例化所有变量,因此您在该语言中不应该遇到该问题。


1
我不确定你在第一段代码中使用了哪个编译器,但它应该会编译通过(通常会有一个警告)。i的值是未定义的,但它确实具有某些值。 - Dolphin
当然两个代码都会编译通过!这正是问题所在。尝试运行第一个代码,你就会明白我所说的“引发异常”的含义了... - Gad
您也不了解这两者之间的区别吗?您能详细说明一下吗? - mfazekas
除非在C/C++中声明int *i,否则不会抛出任何异常,即使在这种情况下它也将比较指针而不是抛出任何异常...就像Dolphin所述,该值未定义,但不会生成任何异常。 - Cobusve
假设i是一个没有正确初始化的随机值,该表达式在C/C++中具有完全相同的语义。 - Raffaello

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接