Skip to main content
ANVISoftware Solutions
Lesson 13 of 62Beginner13 min

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.

Shipping bands as an if chain
C#
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

C#
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 statementConditional expression
What it isA statement that runs codeAn expression that produces a value
Assigning to a variable on the same lineNot possibleYes, which allows the variable to be declared once
Several statements per branchYesNo
Reads well whenThe branches perform different workThe branches pick between two values
Three or more outcomesAn else if chain stays readableNesting gets hard to follow — prefer a switch expression
The same rules written both ways
C#
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.

C#
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.