什么
这是一种用于具有许多隐式类型转换的语言的编码风格(由于他的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
次数:
操作总时间(毫秒)
lhs == null 10521
空== lhs 2346
似乎避免不必要调用的“优化”版本Equals()
快了五倍,但请注意循环计数非常高并且实际实现Equals()
是空的,真正的实现将减少函数调用的相对开销(可能你会除了这个微优化还有其他事情要做)。对于系统类,您不能依赖此详细信息,例如String.Equals()
将始终由操作员调用(无论顺序如何),但您不能假设一个代码路径更快,并且这一事实在框架的未来版本中不会改变(或针对不同的 CPU 架构)。