C# 逻辑顺序和编译器行为

Posted

技术标签:

【中文标题】C# 逻辑顺序和编译器行为【英文标题】:C# logic order and compiler behavior 【发布时间】:2010-09-05 12:54:40 【问题描述】:

在 C# 中(并且可以随意回答其他语言),运行时评估逻辑语句的顺序是什么?

例子:

DataTable myDt = new DataTable();
if (myDt != null && myDt.Rows.Count > 0)

    //do some stuff with myDt

运行时首先评估哪个语句 -

myDt != null

或:

myDt.Rows.Count > 0

?

编译器是否会向后评估语句?也许当涉及“或”运算符时?


& 被称为逻辑位运算符,将始终计算所有子表达式

什么是何时使用按位运算符而不是“短路布尔值”的好例子?

【问题讨论】:

【参考方案1】:

C#:从左到右,如果发现不匹配(评估为假),则停止处理。

【讨论】:

正如其他人所说,&& 运算符的短路发生在 false【参考方案2】:

“C#:从左到右,如果找到匹配项(计算结果为真),则处理停止。”

僵尸羊错了,没有足够的代表投反对票。

问题是关于 && 运算符,而不是 ||运算符。

如果发现 FALSE,则 && 评估将停止。

在 || 的情况下如果找到 TRUE,则评估停止。

【讨论】:

是的。你说得很对。不过,我确实希望以正确的方式解释我的原始答案。我当然无意误导任何人。【参考方案3】:

我知道这个问题已经得到解答,但我想补充一点与该主题相关的信息。

在诸如 C++ 之类的语言中,您实际上可以重载 && 和 || 的行为运营商,强烈建议您不要这样做。这是因为当您重载此行为时,您最终会强制对操作的双方进行评估。这做了两件事:

    它打破了惰性求值机制,因为重载是一个必须调用的函数,因此在调用函数之前对两个参数进行求值。 不能保证所述参数的评估顺序,并且可以是特定于编译器的。因此,对象的行为方式与问题/先前答案中列出的示例中的行为方式不同。

如需了解更多信息,请阅读 Scott Meyers 的书,More Effective C++。干杯!

【讨论】:

【参考方案4】:

vb.net

if( x isNot Nothing AndAlso x.go()) then
    从左到右进行评估 AndAlso 运算符确保只有当左侧为 TRUE 时,才会评估右侧(非常重要,因为 ifx 没有 x.go 会崩溃)

您可以在 vb 中使用 And 代替AndAlso。在这种情况下,左侧也会首先被评估,但无论结果如何,右侧都会被评估。

最佳实践:始终使用 AndAlso,除非您有充分的理由不这样做。


有人问为什么或何时有人使用 And 代替 AndAlso(或 & 代替 &&): 这是一个例子:

if ( x.init() And y.init()) then
   x.process(y)
end 
y.doDance()

在这种情况下,我想同时初始化 X 和 Y。必须初始化 Y 才能使 y.DoDance 能够执行。但是,在 init() 函数中,我还做了一些额外的事情,比如检查套接字是否打开,并且只有当一切正常时,对于 both,我应该继续执行 x.process (y)。

同样,这在 99% 的情况下可能是不需要的,也不是很优雅,这就是为什么我说默认应该使用 AndAlso

【讨论】:

【参考方案5】:

@shsteimer

谦虚所指的概念是运算符重载。在声明中: ... 首先评估 A,如果评估为 false,则永远不会评估 B。这同样适用于

这不是运算符重载。运算符重载是用于定义运算符的自定义行为的术语,例如 *、+、= 等。

这会让你编写自己的“日志”类,然后做

a = new Log(); // Log class overloads the + operator
a + "some string"; // Call the overloaded method - otherwise this wouldn't work because you can't normally add strings to objects.

这样做

a() || b() // be never runs if a is true

其实叫Short Circuit Evaluation

【讨论】:

【参考方案6】:

ZombieSheep 已经死定了。可能正在等待的唯一“问题”是,仅当您使用 && 运算符时,这才是正确的。使用 & 运算符时,每次都会计算两个表达式,无论一个或两个计算结果是否为 false。

if (amHungry & whiteCastleIsNearby)

   // The code will check if White Castle is nearby
   // even when I am not hungry


if (amHungry && whiteCastleIsNearby)

   // The code will only check if White Castle is nearby
   // when I am hungry

【讨论】:

【参考方案7】:

请注意,&& 和 & 在计算表达式的多少方面存在差异。

&& 被称为短路布尔 AND,正如其他人所指出的,如果在计算所有子表达式之前可以确定结果,它将提前停止。

& 被称为逻辑位运算符,并且总是计算所有子表达式。

这样:

if (a() && b())

仅当 a 返回 true 时才会调用 b

但是,这个:

if (a() & b())

将始终同时调用 ab,即使调用 a 的结果为假,因此已知为 不管调用 b 的结果如何。

|| 也存在同样的差异。和 |运营商。

【讨论】:

【参考方案8】:

某些语言有一些有趣的情况,即表达式以不同的顺序执行。我特别想到了 Ruby,但我确信他们是从其他地方(可能是 Perl)借来的。

逻辑中的表达式将保持从左到右,但例如:

puts message unless message.nil?

上面将评估“message.nil?”首先,如果它的计算结果为假(除非像 if 一样,除非它在条件为假而不是真时执行),“puts message”将执行,它将消息变量的内容打印到屏幕上。

有时这是一种有趣的代码结构方式……我个人喜欢将它用于像上面这样非常短的 1 行。

编辑:

为了更清楚一点,上面同:

unless message.nil?
  puts message
end

【讨论】:

【参考方案9】:

左边的,如果为空则停止。

编辑:在 vb.net 中,除非您使用 AndAlso,否则它将评估两者并可能引发错误

【讨论】:

【参考方案10】:

谦虚所指的概念是运算符重载。在声明中:

if( A && B)
    // do something

首先评估A,如果评估为假,则永远不会评估B。这同样适用于

if(A || B)
    //do something

首先评估A,如果评估为真,则永远不会评估B。

这个概念,重载,适用于(我认为)所有 C 风格的语言,以及许多其他语言。

【讨论】:

【参考方案11】:

不,至少 C# 编译器不能向后工作(在 && 或 || 中)。从左到右。

【讨论】:

【参考方案12】:

当一切都在行中时,它们从左到右执行。

当事物嵌套时,它们是从内到外执行的。这可能看起来令人困惑,因为通常“最内层”位于该行的右侧,所以它似乎在倒退......

例如

a = Foo( 5, GetSummary( "Orion", GetAddress("Orion") ) );

事情是这样发生的:

使用文字"Orion" 调用GetAddress 使用文字"Orion"GetAddress 的结果调用GetSummary 使用文字5GetSummary 的结果调用Foo 将此值分配给a

【讨论】:

【参考方案13】:

我喜欢 Orion 的回答。我要补充两点:

    从左到右仍然首先应用 inner-to-outer 确保在调用函数之前解析所有参数

假设我们有以下示例:

a = Foo(5, GetSummary("Orion", GetAddress("Orion")),
           GetSummary("Chris", GetAddress("Chris")));

执行顺序如下:

    GetAddress("Orion") GetSummary("Orion", ...) GetAddress("Chris") GetSummary("Chris", ...) Foo(...) 分配给a

我不能谈论 C# 的法律要求(尽管在写这篇文章之前我确实使用 Mono 测试了一个类似的示例),但是这个顺序在 Java 中是有保证的。

为了完整起见(因为这也是一个与语言无关的线程),有像 C 和 C++ 这样的语言,除非有序列点,否则不能保证顺序。参考文献:1、2。然而,在回答线程的问题时,&&|| 是 C++ 中的序列点(除非重载;另请参阅 OJ 的出色答案)。举几个例子:

foo() && bar() foo() & bar()

&& 的情况下,foo() 保证在bar() 之前运行(如果后者完全运行),因为&& 是一个序列点。在& 的情况下,没有这样的保证(在C 和C++ 中),实际上bar() 可以在foo() 之前运行,反之亦然。

【讨论】:

【参考方案14】:

什么是何时使用按位运算符而不是“短路布尔值”的好例子?

假设你有标志,比如文件属性。假设您已将 READ 定义为 4,将 WRITE 定义为 2,并将 EXEC 定义为 1。在二进制中,即:

READ  0100  
WRITE 0010  
EXEC  0001

每个标志都设置了一个位,并且每个位都是唯一的。位运算符让您可以组合这些标志:

flags = READ & EXEC; // value of flags is 0101

【讨论】:

【参考方案15】:

我在某处听说编译器向后工作,但我不确定这是多么真实。

【讨论】:

【参考方案16】:

当您特别想要评估所有子表达式时,您使用 & 很可能是因为它们具有您想要的副作用,即使最终结果将是 false 并且因此不会执行您的 then 您的 if 语句的一部分。

注意 & 和 |对位掩码和布尔值都起作用,而不仅仅是位操作。它们按位调用,但在 C# 中它们是为整数和布尔数据类型定义的。

【讨论】:

【参考方案17】:

@csmba:

有人问为什么或什么时候有人会使用 And 而不是 AndAlso(或 & 而不是 &&):这是一个例子:

if ( x.init() And y.init()) then
   x.process(y)
end 
y.doDance()

在这种情况下,我想同时初始化 X 和 Y。必须初始化 Y 才能使 y.DoDance 能够执行。但是,在 init() 函数中,我还做了一些额外的事情,比如检查套接字是否打开,只有当这一切正常时,我才应该继续执行 x.process(y)。

我认为这相当令人困惑。尽管您的示例有效,但这不是使用And 的典型情况(我可能会以不同的方式编写它以使其更清晰)。 And(在大多数其他语言中为&)实际上是按位与运算。您将使用它来计算位操作,例如删除标志位或屏蔽和测试标志:

Dim x As Formatting = Formatting.Bold Or Formatting.Italic
If (x And Formatting.Italic) = Formatting.Italic Then
    MsgBox("The text will be set in italic.")
End If

【讨论】:

【参考方案18】:

D 编程语言确实left-to-right evaluation with short circuiting 和doesn't 允许&& and '||' 运算符的重载。

【讨论】:

以上是关于C# 逻辑顺序和编译器行为的主要内容,如果未能解决你的问题,请参考以下文章

为啥 C# 编译器不抛出 null 的逻辑比较?

ref readonly 的 C# 行为

条件编译在 C# 中的行为不正确

未定义行为与求值顺序

C#入门详解

常量和编译时评估 - 为啥要改变这种行为