Logical Operators

Combining true and false with &&, || and !, and why the short-circuiting versions are the ones to reach for.

Syntaxa && b a || b !a a & b a | b a ^ b

A 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

Example · csharp
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 False

When 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.

Example · csharp
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
// True

Exclusive 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.

Example · csharp
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
// 5

Precedence 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.

Example · csharp
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

  • Be the first to comment on this lesson.