Logical Operators
Combining true and false with &&, || and !, and why the short-circuiting versions are the ones to reach for.
a && b a || b !a a & b a | b a ^ bA comparison produces a bool, which is either true or false. Logical operators combine those answers. && means and: the result is true only when both sides are true. || means or: the result is true when at least one side is true. ! in front of a bool flips it.
Short-circuiting
&& evaluates its left side first. If that is false, the answer is already false, so the right side is never evaluated at all. || is the mirror image: a true left side means the right side is skipped. That skipping is called short-circuiting.
C# also has & and |, which produce the same answers but always evaluate both sides. ^ is exclusive or: true when exactly one side is true.
Where the difference matters
Short-circuiting is not only about saving work. It is what makes a guard safe. In name != null && name.Length > 2, the second test is never reached while name is null. Written with a single &, the same line compiles and then throws NullReferenceException, because the right side runs regardless. The same holds for a division guarded by a check against zero.
What trips people up
On whole numbers, &, | and ^ are a different operation: they combine the individual bits, so 6 & 3 is 2. And && binds tighter than ||, so a || b && c means a || (b && c). Brackets cost nothing.
Example
int age = 20;
string? nickname = null;
// ! flips a bool. age < 18 is false, so this prints True.
Console.WriteLine(!(age < 18));
// && is true only when both sides are true.
Console.WriteLine(age >= 18 && age < 65);
// || is true when at least one side is true.
Console.WriteLine(age < 13 || age > 65);
// The left side is false, so the right side is never evaluated and
// nickname.Length is never reached. This is a guard.
bool hasUsableNickname = nickname != null && nickname.Length > 2;
Console.WriteLine(hasUsableNickname);
// & and | answer the same question but always run both sides.
bool hasPermission = age > 100; // false
int timesRun = 0;
bool withAndAnd = hasPermission && ++timesRun > 0;
Console.WriteLine($"&& left timesRun at {timesRun}, result {withAndAnd}");
timesRun = 0;
bool withSingleAnd = hasPermission & ++timesRun > 0;
Console.WriteLine($"& left timesRun at {timesRun}, result {withSingleAnd}");
// Output:
// True
// True
// False
// False
// && left timesRun at 0, result False
// & left timesRun at 1, result FalseWhen to use it
- Check a value is non-null before reading one of its members, in one line.
- Allow an action when the user owns the record or has the admin role.
- Validate that a number is inside a range before using it as an index.
- Skip an expensive lookup unless a cheap flag test has already passed.
More examples
A guard that prevents an exception
The order of the two sides is part of the correctness here, not a style choice: the cheap, safe test goes on the left and the risky one on the right. Any time the right-hand side would throw, loop over a database or cost real time, write && rather than & and put the check first.
int total = 90;
int count = 0;
// count != 0 is false, so total / count is never evaluated.
bool averageIsHigh = count != 0 && total / count > 10;
Console.WriteLine(averageIsHigh);
// The same line with a single & would evaluate the division and throw
// DivideByZeroException:
// bool broken = count != 0 & total / count > 10;
count = 3;
Console.WriteLine(count != 0 && total / count > 10); // 30 > 10
// Output:
// False
// TrueExclusive or, and the bitwise meaning
^ on two bools is the test for "one or the other, but not both", which is how you validate a pair of options where exactly one must be supplied. The second half shows the same operators doing bit arithmetic on numbers. That is a separate job, and it is the reason & and | exist at all; later lessons use it to combine the values of an enum.
bool left = true;
bool right = false;
// ^ is true when exactly one side is true. For bool it matches !=.
Console.WriteLine(left ^ right);
Console.WriteLine(left ^ left);
// Given whole numbers, the same three symbols combine bits instead.
// 6 is 110 and 3 is 011 in binary.
Console.WriteLine(6 & 3);
Console.WriteLine(6 | 3);
Console.WriteLine(6 ^ 3);
// Output:
// True
// False
// 2
// 7
// 5Precedence between && and ||
Two expressions with the same words and the same operators give opposite answers. Mixing && and || in one permission check without brackets is a common source of access bugs, because the version the compiler reads is not the version the author read. Write the brackets even when they change nothing.
bool isAdmin = true;
bool isOwner = true;
bool isPublished = false;
// && binds tighter than ||, so this reads as isAdmin || (isOwner && isPublished).
Console.WriteLine(isAdmin || isOwner && isPublished);
// Brackets say what you mean.
Console.WriteLine((isAdmin || isOwner) && isPublished);
// Comparisons bind tighter than && and ||, so each test needs no brackets.
int score = 55;
Console.WriteLine(score >= 50 && score < 60);
// Output:
// True
// False
// True
Discussion