内部的“If(.. || ..)”和“If(… && …)”构造中发生了什么?
我只是想知道“ if OR ”和“ if AND ”内部会发生什么。 我觉得使用&&
和||
只是语法糖 并且在内部所有案例都构建为单个if语句。
紧凑forms||:
if(a || b || c) { DoSomething(); }
潜在的内部forms:
if(a) { DoSomething(); } else if(b) { DoSomething(); } else if(c) { DoSomething(); }
紧凑forms&&:
if(a && b && c) { DoSomething(); }
潜在的内部forms:
if(a) { if(b) { if(c) { DoSomething(); } } }
这两个例子的表现有什么不同吗?
*编辑:将其他内容添加到|| 案件
首先, ||
和&&
是短路的 。 这意味着:
if(a || b || c) DoSomething();
如果a
为真,则不评估b
和c
。
其次,你执行了||
是假的:
if(a) DoSomething(); if(b) DoSomething(); if(c) DoSomething();
DoSomething()
最多会被调用3次 。
它应该是:
if(a) DoSomething(); else if(b) DoSomething(); else if(c) DoSomething();
要完成,如果您希望在您的条件中首先选择较短的呼叫:
if(aShortFunctionToExecute() || aVeryVeryLongFunctionToExecute()) DoSomething();
会比快
if(aVeryVeryLongFunctionToExecute() || aShortFunctionToExecute()) DoSomething();
因为懒惰的评价
如果您反汇编代码:
private static void Main() { if (a() && b() && c()) { Console.WriteLine("DoSomething"); } } bool a(){ return true; } bool b(){ return 3 % 2 == 1; } bool c(){ return (3 % 2) / 1 == 1; }
你会得到:
if (a() && b() && c()) 00000022 call FFFFFFFFFFEE8D90 00000027 mov byte ptr [rbp+20h],al 0000002a movzx eax,byte ptr [rbp+20h] 0000002e test eax,eax 00000030 je 000000000000005A 00000032 call FFFFFFFFFFEE8D98 00000037 mov byte ptr [rbp+21h],al 0000003a movzx eax,byte ptr [rbp+21h] 0000003e test eax,eax 00000040 je 000000000000005A 00000042 call FFFFFFFFFFEE8DA0 00000047 mov byte ptr [rbp+22h],al 0000004a movzx ecx,byte ptr [rbp+22h] 0000004e xor eax,eax 00000050 test ecx,ecx 00000052 sete al 00000055 mov dword ptr [rbp+24h],eax 00000058 jmp 0000000000000062 0000005a nop 0000005b mov dword ptr [rbp+24h],1 00000062 nop 00000063 movzx eax,byte ptr [rbp+24h] 00000067 mov byte ptr [rbp+2Fh],al 0000006a movzx eax,byte ptr [rbp+2Fh] 0000006e test eax,eax 00000070 jne 0000000000000087 { 00000072 nop Console.WriteLine("DoSomething"); 00000073 mov rcx,12603398h 0000007d mov rcx,qword ptr [rcx] 00000080 call 00000000577A82A0 00000085 nop }
并为代码:
private static void Main() { if (a()) if(b()) if(c()) Console.WriteLine("DoSomething"); } static bool a(){ return true; } static bool b(){ return 3 % 2 == 1; } static bool c(){ return (3 % 2) / 1 == 1; }
你会得到:
if (a()) 00000022 call FFFFFFFFFFEE8D90 00000027 mov byte ptr [rbp+20h],al 0000002a movzx ecx,byte ptr [rbp+20h] 0000002e xor eax,eax 00000030 test ecx,ecx 00000032 sete al 00000035 mov dword ptr [rbp+24h],eax 00000038 movzx eax,byte ptr [rbp+24h] 0000003c mov byte ptr [rbp+3Fh],al 0000003f movzx eax,byte ptr [rbp+3Fh] 00000043 test eax,eax 00000045 jne 00000000000000A4 if(b()) 00000047 call FFFFFFFFFFEE8D98 0000004c mov byte ptr [rbp+28h],al 0000004f movzx ecx,byte ptr [rbp+28h] 00000053 xor eax,eax 00000055 test ecx,ecx 00000057 sete al 0000005a mov dword ptr [rbp+2Ch],eax 0000005d movzx eax,byte ptr [rbp+2Ch] 00000061 mov byte ptr [rbp+3Fh],al 00000064 movzx eax,byte ptr [rbp+3Fh] 00000068 test eax,eax 0000006a jne 00000000000000A4 if(c()) 0000006c call FFFFFFFFFFEE8DA0 00000071 mov byte ptr [rbp+30h],al 00000074 movzx ecx,byte ptr [rbp+30h] 00000078 xor eax,eax 0000007a test ecx,ecx 0000007c sete al 0000007f mov dword ptr [rbp+34h],eax 00000082 movzx eax,byte ptr [rbp+34h] 00000086 mov byte ptr [rbp+3Fh],al 00000089 movzx eax,byte ptr [rbp+3Fh] 0000008d test eax,eax 0000008f jne 00000000000000A4 Console.WriteLine("DoSomething"); 00000091 mov rcx,125D3398h 0000009b mov rcx,qword ptr [rcx] 0000009e call 00000000577B82A0 000000a3 nop
这有点长:它需要40条指令而不是31条指令。
正如thanosqr所指出的,性能还取决于您的条件成立的概率。 举个例子:
如果a
99%的时间失败并且需要1秒才能运行,并且如果b
99%的时间内成功并且需要10秒才能运行,那么超过100次尝试你将更快地放入b
:
if(b || a) => 10s 99% ==> 100 runs will take 99*10+11 = 1001s if(b || a) => 11s 1% if(a || b) => 11s 99% ==> 100 runs will take 99*11+1 = 1090s if(a || b) => 1s 1%
另外,我建议你阅读“ 为什么处理排序数组比未排序数组更快? ”这非常有趣!
使用紧凑forms,C#编译器发出的IL将不那么冗长,导致在运行时处理的指令更少。 发出的IL语句及其逻辑实际上是相同的,因此没有花哨的内置支持来处理该情况或一些特殊指令(请记住,您可以将任何带有布尔结果的表达式放入if
)。
对于使用||
的紧凑forms operator(调试版本):
.method private hidebysig static void One() cil managed { // Code size 38 (0x26) .maxstack 2 .locals init ([0] bool CS$4$0000) IL_0000: nop IL_0001: ldsfld bool ConsoleApplication4.Program::a IL_0006: brtrue.s IL_0019 IL_0008: ldsfld bool ConsoleApplication4.Program::b IL_000d: brtrue.s IL_0019 IL_000f: ldsfld bool ConsoleApplication4.Program::c IL_0014: ldc.i4.0 IL_0015: ceq IL_0017: br.s IL_001a IL_0019: ldc.i4.0 IL_001a: nop IL_001b: stloc.0 IL_001c: ldloc.0 IL_001d: brtrue.s IL_0025 IL_001f: call void ConsoleApplication4.Program::DoSomething() IL_0024: nop IL_0025: ret } // end of method Program::One
使用您的内部表单(考虑到您使用的是else if
而不是if
):
.method private hidebysig static void Two() cil managed { // Code size 60 (0x3c) .maxstack 2 .locals init ([0] bool CS$4$0000) IL_0000: nop IL_0001: ldsfld bool ConsoleApplication4.Program::a IL_0006: ldc.i4.0 IL_0007: ceq IL_0009: stloc.0 IL_000a: ldloc.0 IL_000b: brtrue.s IL_0015 IL_000d: call void ConsoleApplication4.Program::DoSomething() IL_0012: nop IL_0013: br.s IL_003b IL_0015: ldsfld bool ConsoleApplication4.Program::b IL_001a: ldc.i4.0 IL_001b: ceq IL_001d: stloc.0 IL_001e: ldloc.0 IL_001f: brtrue.s IL_0029 IL_0021: call void ConsoleApplication4.Program::DoSomething() IL_0026: nop IL_0027: br.s IL_003b IL_0029: ldsfld bool ConsoleApplication4.Program::c IL_002e: ldc.i4.0 IL_002f: ceq IL_0031: stloc.0 IL_0032: ldloc.0 IL_0033: brtrue.s IL_003b IL_0035: call void ConsoleApplication4.Program::DoSomething() IL_003a: nop IL_003b: ret } // end of method Program::Two
因此,有更多的指令来处理额外的if
语句所需的所有跳转。 因此,第一种forms更有效(实际上更具可读性:))。
在性能方面(每个方法使用10.000.000迭代测量10次,并删除最高和最低值,发布版本):
紧凑forms:平均55ms
详细forms:平均56ms
所以没有太大的区别。
对于那些阅读C#比汇编更好的人来说,真正的内部forms更接近于:
if(a) goto yes; if(b) goto yes; if(c) goto yes; goto no; yes: DoSomething(); goto done; no: /* if there were an else it would go here */; done: ;
对于
if(a || b || c) DoSomething();
和
if(!a) goto no; if(!b) goto no; if(!c) goto no; yes: DoSomething(); goto done; no: /* if there were an else it would go here */; done: ;
对于
if(a && b && c) DoSomething();
这是因为实际指令是条件分支 – 在内部forms中, if
不可能与块,嵌套if
或实际上除goto
之外的任何东西相关联。
代码:
if(a) if(b) if(c) DoSomething();
是一个逻辑(但不是“实际”)等价物:
if(a && b && c) DoSomething();
对于OR
运算符,你有点错误。 逻辑(但同样不是“实际”)等同于:
if(a || b || c) DoSomething();
将会:
if(a) DoSomething(); else if(b) DoSomething(); else if(c) DoSomething();
根据实际差异,我理解编译器引入的任何结果代码差异(有关详细信息,请参阅其他答案)。
||
和&&
是条件运算符。 他们也是运营商,就像你可能知道的其他运营商一样。 (例如+
, *
,…)
他们的行为类似于逻辑运算符, |
和&
。 它们收到两个bool
类型变量并以这种方式返回bool
值:
// If one of them is true, the return value is true. Otherwise, it's false. true | true == true true | false == true false | true == true false | false == false // If both of them are true, the return value is true. Otherwise, it's false. true & true == true true & false == false false & true == false false & false == false
但是,对于条件运算符,存在一点差异: 短路 。
假设这段代码:
bool func1() { .. } bool func2() { .. } bool b1 = func1() || func2(); bool b2 = func1() && func2();
如果func1()
返回true
,则无论func2()
返回什么, b1
变为true
。 因此,我们不需要调用func2()
而实际上不需要。 如果func1()
返回false
,则对b2
应用相同的内容。 此行为称为短路。
现在,让我们考虑一下你的例子。
if (a || b || c) DoSomething();
它等于
bool value = a || b || c; if (value) DoSomething();
由于条件运算符的求值顺序是从左到右,所以它等于
bool value = (a || b) || c; if (value) DoSomething();
他们的VB等价物可以更具描述性。 ||
是OrElse
和&&
是AndAlso
在VB中。
这些是条件运算符; 这意味着他们制定控制条款 – if
在您的情况下 – 根据需要评估条件而不是所有条件。
例如,在if ( a || b )
如果a
为真,那么b
是什么并不重要; 结果为true,因此b
不会被评估,这将导致更快的执行。
此function也可用作空检查机制。 if ( a != null && a.prop == somevalue )
将阻止Null引用exception,如果a
为null且它不为null,则将访问其prop
属性以评估第二个条件。