什么是Yoda编码风格
它是一种编码风格,用于具有许多隐式类型转换的语言(因为字面上的顺序类似《星球大战》中的角色Yoda,即OSV单词顺序)。有时甚至会被强制执行,并在项目指南中要求使用,以防止由于拼写错误导致的某些错误。不同的语言有不同的变体和扩展,但本回答将仅限于C和C#。
为什么要使用Yoda编码风格
例如,在C语言中,您可以编写:
int a = CalculateValue();
if (a = CalculateAnotherValue()) {
}
该代码将从
CalculateValue()
返回的值赋给
a
,然后用
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)
对于任何非null
参数都返回true
。测试将被循环执行Int32.MaxValue
次:
操作 总时间(毫秒)
lhs == null 10521
null == lhs 2346
似乎“优化”的版本避免了不必要的Equals()
调用,速度快了五倍,但请注意,循环计数非常高,并且Equals()
的实际实现是空的,真正的实现将减少函数调用的相对开销(也可能会有其他微小优化)。对于系统类,你不能依赖这些细节,例如String.Equals()
总是由操作员调用(无论顺序如何),但你不能假设一个代码路径更快,并且这个事实在框架的未来版本(或不同的CPU体系结构)中也不会改变。