Thursday, March 08, 2007 4:50 PM
Every C# programmer knows that you can concatenate conditional statements using &&. Many programmers also know that there is an interesting side effect to this: the second conditional statement (the statement on the right of the &&) will not be executed if the first conditional statement is false. Take the following example...
public class MilkAndCookies
{
public bool GotMilk()
{
Console.WriteLine("Oh no! We're all out of milk!");
return false;
}
public bool GotCookies()
{
Console.WriteLine("But we've got plenty of cookies!");
return true;
}
}
public class Program
{
static void Main(string[] args)
{
MilkAndCookies milkAndCookies = new MilkAndCookies();
if (milkAndCookies.GotMilk() && milkAndCookies.GotCookies())
{
Console.WriteLine("Milk and Cookies for everyone!");
}
}
}
// Outputs...
// "Oh no! We're all out of milk!"
In this snippet, only the first statement is actually executed since it returns false. This effect, known as short circuiting, is a result of the C# compiler optimizing your code based on a principle in boolean algebra. According to the definition of a boolean AND, the AND is only valid if both arguments given to the operator return true. For example...
true = true AND true
false = true AND false
false = false AND true
false = false AND false
Essentially what is happening is that the compiler is taking advantage of the fact that for a && to properly return true, that both conditionals must return true as well. Therefore, once the first conditional has failed this test this is impossible. There is no need to evaluate the second conditional at this point.
In fact, if we look at the IL generated by the C# compiler we can see exactly why our code performs as so...
IL_0001: newobj instance void MilkAndCookies.MilkAndCookies::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: callvirt instance bool MilkAndCookies.MilkAndCookies::GotMilk()
IL_000d: brfalse.s IL_001a
IL_000f: ldloc.0
IL_0010: callvirt instance bool MilkAndCookies.MilkAndCookies::GotCookies()
IL_0015: ldc.i4.0
IL_0016: ceq
IL_0018: br.s IL_001b
IL_001a: ldc.i4.1
IL_001b: stloc.1
IL_001c: ldloc.1
IL_001d: brtrue.s IL_002c
IL_001f: nop
IL_0020: ldstr "Milk and Cookies for everyone!"
IL_0025: call void [mscorlib]System.Console::WriteLine(string)
Take a look at the statement highlighted in red, this statement tells us to jump to instruction IL_001(about six lines down) if the value on the top of the stack is false. In effect, this completely skips execution of GotCookies().
As best practices stress that you should not place anything more than state checkers inside of a conditional statement, this behavior does yield some sneaky side effects if you are depending on some sort of operation in your second conditional to be performed.
public bool GotCookies()
{
Console.WriteLine("But we've got plenty of cookies!");
// Since we have cookies, we should get the plates
GetThePlates();
return true;
}
In our example, GetThePlates() will never be executed since GotMilk() returns false and GotCookies() is short circuited out. This is also a good example of poor separation of concerns since not only does GotCookies() perform more than one task, it also doesn't convey properly to the user that it will get the plates as well. By this token, we can see that proper separation of concerns eliminates the possibility of many of these side effects.
Not many C# programmers know this, but you can actually subvert this optimization by using the & instead. This operator, normally reserved for bitwise operations, can actually be used interchangeable with the &&. Well, almost interchangeably...the fact is that there is one subtle difference between the two: the & operator does not short circuit. Returning to our first example, if we replace the && with & then we actually see the following output...
public class Program
{
static void Main(string[] args)
{
MilkAndCookies milkAndCookies = new MilkAndCookies();
if (milkAndCookies.GotMilk() & milkAndCookies.GotCookies())
{
Console.WriteLine("Milk and Cookies for everyone!");
}
}
}
// Outputs...
// "Oh no! We're all out of milk!"
// "But we've got plenty of cookies!"
Now if the examine the IL for this method, we can see that execution doesn't bail just because the first condition fails...
IL_0001: newobj instance void MilkAndCookies.MilkAndCookies::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: callvirt instance bool MilkAndCookies.MilkAndCookies::GotMilk()
IL_000d: ldloc.0
IL_000e: callvirt instance bool MilkAndCookies.MilkAndCookies::GotCookies()
IL_0013: and
IL_0014: ldc.i4.0
IL_0015: ceq
IL_0017: stloc.1
IL_0018: ldloc.1
IL_0019: brtrue.s IL_0028
IL_001b: nop
IL_001c: ldstr "Milk and Cookies for everyone!"
IL_0021: call void [mscorlib]System.Console::WriteLine(string)
Note the absence of our brfalse.s statement from before. Execution proceeds as normal regardless of the return value of GotMilk().
As said before, it's really not recommended to write any code that depends on code inside of conditional evaluations being executed. It's best to either avoid this sort of code altogether, or just move it outside of the conditionals like so...
public class Program
{
static void Main(string[] args)
{
MilkAndCookies milkAndCookies = new MilkAndCookies();
bool gotMilk = milkAndCookies.GotMilk();
bool gotCookies = milkAndCookies.GotCookies();
if (gotMilk && gotCookies)
{
Console.WriteLine("Milk and Cookies for everyone!");
}
}
}
// Outputs...
// "Oh no! We're all out of milk!"
// "But we've got plenty of cookies!"
However, in the event that you encounter legacy or third party code that seems to make use of this 'technique', this is a pretty good tip to remember.
Addendum (3.21.07):
Also remember that this same principle applies to the OR operators as well. || will perform a short circuiting operation where as | will fully evaluate the conditional regardless of the return value of the first operand. Keep in mind, however, that since we're looking for an OR here we're actually interested in whether or not the first operand is true, not false. Therefore, if the first operand evaluates to true then your conditional will short circuit if you're using the || operator. If your first operand evaluates to false then both conditionals will be evaluated since there is still the possibility of the || returning true if the second conditional returns true.
Special thanks to