In this article, we are going to look at what exactly short circuit evaluation is in C# and how asynchronous calls are affecting it.
NOTE: Some degree of basic understanding of conditions in C# is required. We have an excellent article for a quick refresher on Conditions in C#, do check it out.
Let’s start.
Short Circuit Evaluation
First, we need to understand short circuit evaluation in C#.
In short, short circuit evaluation is a feature that evaluates a logical expression only until we can determine the final result. This means if we can determine the value of the expression without evaluating all the sub-expressions, we do not need to evaluate the remaining sub-expressions.
We use (conditional AND) and (conditional OR) operators to achieve this.
When we use the &&
operator, if the left-hand side of the expression evaluates to false
, the right-hand side expression is not evaluated. That is because the overall result will be false
regardless.
To verify that let’s set up our example and create IUtility
interface:
public interface IUtility { bool FirstCondition(); Task<bool> FirstConditionAsync(); bool SecondCondition(); Task<bool> SecondConditionAsync(); }
And set up our return values:
_utilityMock = new Mock<IUtility>(); _utilityMock.Setup(utility => utility.FirstCondition()).Returns(true); _utilityMock.Setup(utility => utility.FirstConditionAsync()).ReturnsAsync(true); _utilityMock.Setup(utility => utility.SecondCondition()).Returns(false); _utilityMock.Setup(utility => utility.SecondConditionAsync()).ReturnsAsync(false);
Both FirstCondition
and FirstConditionAsync
methods return true
, while both SecondCondition
and SecondCodnitionAsync
methods return false
.
Now, let’s have a look at our example:
if (utility.SecondCondition() && utility.FirstCondition()) { result = true; } Assert.That(result, Is.False); _utilityMock.Verify(utility => utility.SecondCondition(), Times.Once()); _utilityMock.Verify(utility => utility.FirstCondition(), Times.Never());
In this example, we are evaluating the result of the conditional AND operation between SecondCondition
and FirstCondition
. The SecondCondition
method returns false
, so we do not need to evaluate FirstCondition
as the result will be also false
. Thus, we will not execute the block of code inside theif
statement.
On the other hand, if we would use &
operator:
if (utility.SecondCondition() & utility.FirstCondition()) { result = true; } Assert.That(result, Is.False); _utilityMock.Verify(utility => utility.SecondCondition(), Times.Once()); _utilityMock.Verify(utility => utility.FirstCondition(), Times.Once());
The result we get will still be false
, but in contrast, we need to evaluate both conditions.
Next, when we use ||
operator and the left-hand side of the expression evaluates to true
, the right-hand side expression is not evaluated. That is because the overall result will be true
regardless:
if (utility.FirstCondition() || utility.SecondCondition()) { result = true; } Assert.That(result, Is.True); _utilityMock.Verify(utility => utility.FirstCondition(), Times.Once()); _utilityMock.Verify(utility => utility.SecondCondition(), Times.Never());
In this case, we are evaluating the result of the conditional OR operation between FirstCondition
and SecondCondition
. Because FirstCondition
returns true
the second condition will not be evaluated and the final result we will get will be also true
.
And similarly, when we use |
operator instead:
if (utility.FirstCondition() | utility.SecondCondition()) { result = true; } Assert.That(result, Is.True); _utilityMock.Verify(utility => utility.FirstCondition(), Times.Once()); _utilityMock.Verify(utility => utility.SecondCondition(), Times.Once());
We would still get the result as true
, but again we would need to evaluate both conditions.
Asynchronous Short Circuit Evaluation
Now, let’s see what will happen if we use await
keyword in our expression.
As we know we use the await
keyword to wait for the completion of an asynchronous operation before continuing with the execution of the current method.
However, it does not change the evaluation order or the short-circuit behavior of logical expressions.
Firstly, let’s have a look at the example:
if (await utility.SecondConditionAsync() && await utility.FirstConditionAsync()) { result = true; } Assert.That(result, Is.False); _utilityMock.Verify(utility => utility.SecondConditionAsync(), Times.Once()); _utilityMock.Verify(utility => utility.FirstConditionAsync(), Times.Never());
In this example, we have two asynchronous conditions. With short circuit evaluation, because SecondConditionAsync
returns a Task<bool>
that evaluates to false
, we will not need to evaluate FirstConditionAsync
. We will neither await nor execute it.
We could expand our if
statement to the form that visualizes the execution sequence:
bool firstCondition = await utility.FirstConditionAsync(); if (firstCondition) { bool secondCondition = await utility.SecondConditionAsync(); if (secondCondition) { result = true; } }
Similarly, short circuit evaluation applies to the ||
operator.
if (await utility.FirstConditionAsync() || await utility.SecondConditionAsync()) { result = true; } Assert.That(result, Is.True); _utilityMock.Verify(utility => utility.FirstConditionAsync(), Times.Once()); _utilityMock.Verify(utility => utility.SecondConditionAsync(), Times.Never());
Analogous to the previous example, because FirstConditionAsync
returns a Task<bool>
that evaluates to false
, we will not await or execute SecondConditionAsync
.
We can leverage both of those behaviors because the short-circuit behavior is applied before the await
keyword is evaluated.
Conclusion
Short circuit evaluation is a powerful feature in C# that we can use to optimize the evaluation of logic expressions. This is possible thanks to avoiding unnecessary computations. But, we must remember that asynchronous operations are not affecting short-circuit behavior.
Why does your await example evaluate 2nd before 1st? that’s confusing.
We just reversed the order as we did in the sync example as the Second method returns false and it fits the example.