if, else and Conditional Expressions
By the end of this lesson
Branch clearly, including with the conditional operator where it aids readability.
You have already used if and else. What is worth attention in C# is the shape of a branch: where the braces go, what order the conditions come in, and when a decision is better written as an expression that produces a value.
decimal orderTotal = 85m;
bool isMember = true;
decimal shipping;
if (orderTotal >= 100m)
{
shipping = 0m;
}
else if (isMember && orderTotal >= 50m)
{
shipping = 2.99m;
}
else
{
shipping = 4.99m;
}
Console.WriteLine($"Shipping: {shipping:N2}");- Exactly one branch runs. The first condition that is true wins, and the remaining conditions are not even evaluated.
- Order is logic, not style. Move the member branch above the 100 check and a member spending 150 is charged 2.99.
- shipping is declared with no value and assigned in every branch. Remove the else and the build fails, because the compiler cannot prove the variable has a value before it is read.
The conditional operator
int itemCount = 1;
bool isMember = true;
string itemWord = itemCount == 1 ? "item" : "items";
decimal discountRate = isMember ? 0.10m : 0.05m;
Console.WriteLine($"{itemCount} {itemWord} in your basket");
Console.WriteLine($"Discount rate: {discountRate:P0}");- The form is condition ? valueWhenTrue : valueWhenFalse, and the whole thing produces a value — which is why it can sit on the right of an assignment where an if statement cannot.
- Both results must be the same type, or convertible to a single type. That is the compiler making sure the variable has one clear type.
- Use it to choose between two values. When the branches do work rather than produce a value, an if statement says it better.
Which form fits:
| if / else statement | Conditional expression | |
|---|---|---|
| What it is | A statement that runs code | An expression that produces a value |
| Assigning to a variable on the same line | Not possible | Yes, which allows the variable to be declared once |
| Several statements per branch | Yes | No |
| Reads well when | The branches perform different work | The branches pick between two values |
| Three or more outcomes | An else if chain stays readable | Nesting gets hard to follow — prefer a switch expression |
public static void DispatchNested(Order? order)
{
if (order is not null)
{
if (order.LineCount > 0)
{
if (order.IsPaid)
{
Console.WriteLine($"Dispatching {order.Reference}");
}
}
}
}
public static void DispatchFlat(Order? order)
{
if (order is null) return;
if (order.LineCount == 0) return;
if (!order.IsPaid) return;
Console.WriteLine($"Dispatching {order.Reference}");
}- Both methods apply identical rules. The second states each reason to stop once, then gets on with the work at the left margin.
- is not null reads better than != null and is the form you will see in newer code. For your own classes it also avoids any custom equality behaviour, because it tests the reference directly.
- Neither version tells the caller why nothing happened. If that matters, return a result rather than void — which is the subject of a later lesson in this course.
Summary
- Only the first matching branch runs, so the order of conditions is part of the logic
- Write the braces even for a single statement — the second line added later is a real bug
- The conditional operator produces a value, so it fits assignment; an if statement fits work
- Checking exits early keeps the main work at the left margin as rules accumulate
- The compiler will not let a variable be read unless every path assigns it
Practice
Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.
Try it yourself
Try it yourself
Work out a late fee: nothing if the invoice was paid within 30 days, 2 per cent of the total between 31 and 60 days, and 5 per cent after that. Invoices under 10 are never charged a fee at all.
Decide where the under-10 rule belongs before you write it, then check whether your chain gives the right answer for a 5 invoice that is 90 days late.
Show solution
The under-10 rule overrides everything, so it has to be tested first. Put it at the end of the chain and a 5 invoice at 90 days already matched the 5 per cent branch, and the rule never gets a say.
Writing the overriding rule as a separate early check, rather than as another else if, also documents that it is a different kind of rule — an exemption, not a band.
The bands themselves run from the longest overdue period down, so each one only has to state its own lower bound.
decimal invoiceTotal = 5m;
int daysOverdue = 90;
decimal lateFee;
if (invoiceTotal < 10m)
{
lateFee = 0m; // exempt, whatever the delay
}
else if (daysOverdue > 60)
{
lateFee = invoiceTotal * 0.05m;
}
else if (daysOverdue > 30)
{
lateFee = invoiceTotal * 0.02m;
}
else
{
lateFee = 0m;
}
Console.WriteLine($"Late fee: {lateFee:N2}");Think about it
Think about it
A conditional expression can be nested inside another conditional expression, which makes a three-way choice fit on one line.
Why is that usually worse than an if chain, even though it is shorter?
Show solution
Reading a nested conditional means tracking which ? pairs with which :, and the conditions no longer line up under each other. Three outcomes is about where most readers lose the thread.
It also changes badly. Adding a fourth outcome means rewriting the whole line, so the change is harder to review than a new branch in a chain would be.
When the branches all produce a value, a switch expression gives the compactness with the conditions listed one per line. That is usually the better answer than either option here.
Saved in this browser only.