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

时间:2012-05-30 06:59:10

标签: c# .net performance

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

Request.QueryString["xxxx"] != null

最近有人说

null != Request.QueryString["xxxx"]

提供更好的表现。

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

注意〜以上只是一个例子。一般而言

是否

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

优于

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

由于

4 个答案:

答案 0 :(得分:15)

什么

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

为什么是

例如在C中你可以写:

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

此代码将a分配给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#中通常并不重要。这是来自C / C ++的继承的实践。因为在C#表达式中没有自动转换为bool,所以下面的代码将无法编译:

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

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

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

效果

从这些功能开始:

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)只会为任何非true参数返回null。测试将循环Int32.MaxValue次:

Operation       Total time (ms)
lhs == null               10521
null == lhs                2346

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

答案 1 :(得分:4)

不,一般情况下都不是这样。比较运算符需要评估它们的两侧,因此在左侧放置常量没有任何好处。但是将它们放在左侧(称为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

答案 2 :(得分:2)

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

if (N == var) ...

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

if (N = var)

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

如果是

if (var = N)

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

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

答案 3 :(得分:0)

Constant [Operator] Actual Value被称为Yoda Conditions,因为它就像“如果它是蓝色的 - 这是天空”或“如果那个很高 - 这是一个男人。”

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

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