在文字值的相等比较中,操作数的顺序是否重要?

我习惯把代码写成(只是一个例子)

Request.QueryString["xxxx"] != null 

最近有人这么说

 null != Request.QueryString["xxxx"] 

提供更好的表现。

我很想知道它是否真的带来了任何不同,如果是这样的话怎么样?

注意〜以上只是一个例子。 一般来说

是否

 Constant [Operator] Actual Value (eg 1 == Convert.ToInt32(textbox1.text)) 

比…更好

 Actual Value [Operator] Constant (eg Convert.ToInt32(textbox1.text) == 1) 

谢谢

不,一般情况下都不是这样。 比较运算符需要评估它们的两侧,因此在左侧放置常量没有任何好处。 但是将它们放在左侧(称为Yoda样式)可以减少语言中的编码错误,在这些语言中,您可以在条件内使用赋值运算符,并且您无意中错误地将比较运算符==错误输入为单个=

 // What you intended to write if (a == 6) ... // What you wrote instead if (a = 6) ... // --> always true as the value of a is changed to 6 // What if Yoda style is used if (6 = a) ... // --> compile time error 

什么

这是一种用于具有许多隐式类型转换的语言的编码风格(由于他的OSV单词顺序,友好地命名为星球大战角色的Yoda)。 有时它甚至被项目指南强制执行和要求,以防止从拼写错误中产生某种错误。 不同的语言有这种风格的不同变化和扩展,但我将这个答案限制在C和C#。

为什么是

例如在C中你可以写:

 int a = CalculateValue(); if (a = CalculateAnotherValue()) { /* Do something */ } 

此代码将分配给CalculateValue()返回的值,然后它将使用CalculateAnotherValue()的结果覆盖该值,如果它不为零,则它将执行代码。 可能它不是打算做的, if表达式中的赋值是错误的。 从您的NULL示例,您可能有:

 if (pBuffer = NULL) 

同样可能它不是你想要的(这是一个非常常见的错误),你将为指针指定NULL并且条件将始终为false。 如果你写:

 if (NULL = pBuffer) 

它不会编译(因为您无法为文字赋值),并且您将收到编译时错误。

这种编译时间检查不是使用这种编码风格的唯一原因,看看这个C#(非常常见)的代码:

 if (text != null && text.Equals("something", StringComparison.InvariantCulture) DoSomething(); 

它可以签约:

 if ("something".Equals(text, StringComparison.InvariantCulture)) DoSomething(); 

为什么不

在C#中通常没关系。 这是一种inheritance自C / C ++的实践。 因为在C#表达式中没有自动转换为bool所以下面的代码将无法编译:

 if (Request.QueryString["PartnerID"] = null) 

然后这种做法在C#中没有用(@IlianPinzon在评论中指出了例外),没有关于性能的信息,它只用于避免这种错误。

关于为什么是部分中的最后一个例子,问题是可读性,写"something".Equals(text)就像是说“如果快乐就是那个人”而不是“如果这个人快乐”。

性能

从这些function开始:

 static bool TestRight(object value) { return value == null; } static bool TestLeft(object value) { return null == value; } 

他们产生以下IL:

 .maxstack 2 .locals init ([0] bool CS$1$0000) L_0000: nop L_0001: ldnull L_0002: ldarg.0 L_0003: ceq L_0005: stloc.0 L_0006: br.s L_0008 L_0008: ldloc.0 L_0009: ret 

唯一的区别在于行L_0001和L_0002,它们只是交换,但其操作数的顺序不会改变ceq的行为。 即使您重写Equals()方法,JIT编译器也会为两个表达式生成相同的汇编代码(因为比较将始终由Equals()null无类型的 )。

如果比较涉及用户定义的相等比较器,则事情可能会更复杂,在这种情况下,没有规则,它将取决于有效的op_Equals实现。 例如,这==实现:

 public static bool operator==(MyType lhs, MyType rhs) { if (Object.ReferenceEquals(lhs, rhs)) return true; if (Object.ReferenceEquals(lhs, null)) return false; return lhs.Equals(rhs); } 

在这种情况下,如果第一个操作数为null则执行速度会稍快一些(因为MyType.Equals()甚至不会被调用)但是这个性能增益非常小:保存一个比较,几个跳转和一个虚函数调用。 此外,你可以在相反的情况下重写函数更快(如果你真的很重要)。

这里是一个小测试,其中MyType.Equals(object)只是为任何非null参数返回true 。 测试将循环Int32.MaxValue次:

 操作总时间(ms)
 lhs == null 10521
 null == lhs 2346

似乎“优化”版本避免了对Equals()不必要调用快了五倍但注意循环计数非常高并且Equals()实际实现是空的,真正的实现将减少函数调用的相对开销(也许除了这种微优化之外你还有别的东西可以做)。 对于系统类,您不能依赖于这些细节,例如String.Equals()将始终由运算符调用(无关紧要的顺序),但您不能假设一个代码路径更快,并且这个事实不会改变框架的未来版本(或不同的CPU架构)。

这是一个风格问题。 有人喜欢写作

 if (N == var) ... 

只是因为如果你错过了一个= (初学者的常见情况)

 if (N = var) 

其中N是常量,编译器将给出错误。

的情况下

 if (var = N) 

它可能会给你一个警告,但可能不会(取决于编译器和标志)。 所以有时候很难找到问题所在。

两种变体的性能是相同的。 选择你的风格并遵循它。

Constant [Operator] Actual Value被称为尤达条件,因为它就像“如果它是蓝色 – 这是天空”或“如果它是高的 – 这是一个人”。

使用这种条件样式在C / C ++中很流行,你可以在typed =而不是==时为变量赋值。 在C#中使用Yoda条件是没有意义的。

我也怀疑它有更好的性能。 即使它具有一些性能优势,它也会非常小。 匹配小于可读性的缺点。