I was just wondering what happens inside of an \"if OR\" and \"if AND\". I have a feeling that it\'s just syntactic sugar to use &
First of all, || and && are short-circuit. Which mean that in:
if(a || b || c)
DoSomething();
if a is true, b and c will not be evaluated.
Secondly, your implementation of the || is false:
if(a)
DoSomething();
if(b)
DoSomething();
if(c)
DoSomething();
DoSomething() will be called up to 3 times.
It should be:
if(a)
DoSomething();
else if(b)
DoSomething();
else if(c)
DoSomething();
To finish, if you want performance prefer shorter call first in your conditions:
if(aShortFunctionToExecute() || aVeryVeryLongFunctionToExecute())
DoSomething();
Will be faster than
if(aVeryVeryLongFunctionToExecute() || aShortFunctionToExecute())
DoSomething();
Because of lazy-evaluation
If you disassemble the code of:
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;
}
You'll get:
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
}
and for the code:
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;
}
You'll get:
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
Which is a bit longer: it takes 40 instructions instead of 31.
As pointed out by thanosqr, the performance also depend of the probability for your condition to be true. To take his example:
If a fails 99% of the time and take 1 sec to run and if b
succeed 99% of the time and take 10 sec to run, over 100 tries you'll be faster putting b first:
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%
Also, I suggest you this reading " Why is it faster to process a sorted array than an unsorted array? " which is quite interesting!
Using the compact form, the IL emitted by the C# compiler will be less verbose, resulting in less instructions to be handled at runtime. The emitted IL statements and their logic are actually the same, so there's no fancy built-in support to handle that case or some special instruction (keep in mind that you can put any expression with a boolean result into an if).
For the compact form using the || operator (debug build):
.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
With your internal form (considering you're using else if instead of an 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
So there are way more instructions to handle all the jumps required by the additional if statements. The first form is therefore more efficient (and actually more readable :)).
In terms of performance (each method measured 10 times with 10.000.000 iterations and removed the highest and lowest values, release build):
Compact form: 55ms in average
Verbose form: 56ms in average
So there's no big difference at all.
The code:
if(a)
if(b)
if(c)
DoSomething();
is a logical (but not "practical") equivalent for:
if(a && b && c)
DoSomething();
As for the OR operator you've got it a bit wrong. A logical (but, again, not "practical") equivalent for:
if(a || b || c)
DoSomething();
would be:
if(a)
DoSomething();
else if(b)
DoSomething();
else if(c)
DoSomething();
By practical difference I understand any resulting code differences introduced by the compiler (refer to other answers for details).
Their VB equivalents can be more describing. || is OrElse and && is AndAlso in VB.
These are conditional operators; meaning they make the control clause - if in your case - evaluate the conditions as needed and not all of them always.
For example, in if ( a || b ) if a is true, it doesn't matter what b is; the result is true and therefore b will not get evaluated and this will result in faster execution.
This feature can be used as a null-checking mechanism too. if ( a != null && a.prop == somevalue ) will prevent a Null Reference Exception if a is null and if it's not null, its prop property will be accessed to evaluate the second condition.
|| and && are conditional-operators. They're also operators, like other operators you might know. (e.g. +, *, ...)
Their behavior is similar to logical-operators, | and &. They receive two bool type variables and return bool value in this way:
// 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
However, as for conditional-operators, there's a bit difference: short-circuit.
Suppose this code:
bool func1() { .. }
bool func2() { .. }
bool b1 = func1() || func2();
bool b2 = func1() && func2();
If func1() returns true, b1 becomes true regardless of what func2() returns. Therefore, we don't need to call func2() and actually don't. If func1() returns false, the same thing is applied to b2. This behavior is called short-circuit.
Now, let's think about your example.
if (a || b || c)
DoSomething();
It's equal to
bool value = a || b || c;
if (value)
DoSomething();
Since the order of evaluation of conditional operators is left-to-right, it's equal to
bool value = (a || b) || c;
if (value)
DoSomething();
For those who read C# better than assembly, the real internal forms are closer to:
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: ;
for
if(a || b || c)
DoSomething();
and
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: ;
for
if(a && b && c)
DoSomething();
That's because the actual instructions are conditional branches -- in the internal form it is not possible for an if to be associated with a block, a nested if, or actually anything except a goto.