How to Refactor Nested if Statements in C#?


The if statement is one of the simplest control structures in the C# language.

It is actually the preferred way of implementing conditional logic in C#. However, when you use it in a nested fashion, you run the risk of making your code confusing and difficult to read for the next programmer who has to modify it.

So, how do you refactor multiple nested if statements? The easiest possible way is to use guard clauses. A guard clause is an if statement that checks for a condition and favors early exit from the current method. If the condition is satisfied, the if block returns from the method.

You will easily understand this refactoring once I show you an example, so keep reading!

What is the problem with nested if statements?

I am sure you are familiar with this typical C# code pattern:

if (something is true) 
{ 
    if (another something is true) { return doSomething(); } 
}

You see it everywhere, especially in legacy code: to get to our desired output, we need to evaluate multiple things, and we aren’t sure exactly how many. It’s hard to see what we are dealing with here. How many execution paths the code contains? And what if we want to change it? Are we going to have to hunt down every instance of this pattern in the codebase to make sure we didn’t miss anything?

The nested conditional like this leaves more questions than answers. This is a code smell, meaning the code is not in the optimal state. The best way is to eliminate this kind of conditional logic. Let’s see an example of how to use a guard clause to eliminate nested ifs.

How would you refactor nested if statements?

To eliminate a nested conditional statement, you can use a guard clause.

A guard clause is a condition within the if statement that must be met for code execution to continue. If the condition isn’t met, then no further processing is done.

The guard clause favors an early return from the method.

Let’s start by examining the example that needs refactoring.

public class ReplaceNestedIfStatementsWithGuardClauses
{
    public double GetInterestRate(ClientType clientType)
    {
        double result;
        if (clientType == ClientType.Business)
        {
            result = 0.05;
        }
        else
        {
            if (clientType == ClientType.ExistingClient)
            {
                result = 0.02;
            }
            else
            {
                result = 0.03;
            }
        }
        return result;
    }
}

public enum ClientType
{
    Business,
    ExistingClient,
    NewClient
}

We have the GetInterestRate method. It takes one parameter, and that is the client type. ClientType is an enum, and possible cases are:

  • business
  • existing client and
  • new client

If the client type is business type, then the interest rate is 0.05. Otherwise, if we have an existing client, the interest rate is 0.02. The only remaining case is if a client is a new client, then the interest rate is 0.03.

There are two issues with this code:

  1. we have a nested if statement
  2. this collecting variable result.

We have the result variable where we are storing temporary outcomes. In the end, we are returning that result, which complicates the code.

We can use the guard close, eliminate nested ifs, and the need for the collecting variable. We start with the first if conditional statement. Here we will introduce the guard close.

Instead of this:

if (clientType == ClientType.Business)
{
    result = 0.05;
}

We now have a guard clause. Inside the if block, we return 0.05 from the method.

if (clientType == ClientType.Business)
{
    return 0.05;
}

After this, the else statement is not needed in the code.

And now, what we can do is to continue and apply the second guard clause. If the client type is an existing type, then we won’t be storing this in the result variable. But instead, what we will do is return 0.02.

public class ReplaceNestedIfStatementsWithGuardClauses
{
    public double GetInterestRate(ClientType clientType)
    {
        double result;
        if (clientType == ClientType.Business)
        {
            return 0.05;
        }

        if (clientType == ClientType.ExistingClient)
        {
            return 0.02;
        }
        else
        {
            result = 0.03;
        }

        return result;
    }
}

Now the only other possible outcome is that our client type is a new client. This means we can simply return 0.03.

public double GetInterestRate(ClientType clientType)
{
    double result;
    if (clientType == ClientType.Business)
    {
        return 0.05;
    }
    if (clientType == ClientType.ExistingClient)
    {
        return 0.02;
    }

    return 0.03;
}

Now it’s safe to eliminate the result variable since it’s not used anymore. Visual Studio will also mark the variable as unneeded.

Invert if – Visual Studio built-in refactoring command

Invert if is a Visual Studio built-in refactoring command. It inverts the Boolean logic of the if statement. The Invert if command shifts the conditional expression from the true to false branch of an if statement and vice versa.

The Invert if command is available in newer versions of Visual Studio, including 2019. The command is available at the top of the Refactoring menu, but it also available at the shortcut menu when you right-click the if statement.

This refactoring is great when you want to refactor a long if-else statement into a series of smaller if statements.

Let’s see this in action.

public static bool IsFibonnaciNumber(int number)
{
    if (number != 0)
    {
        if (number != 1)
        {
            if (number != 2)
            {
                double fi = (1 + Math.Sqrt(5)) / 2.0;
                int n = (int)Math.Floor(Math.Log(number * Math.Sqrt(5) + 0.5, fi));

                int actualFibonacciNumber = (int)Math.Floor(Math.Pow(fi, n) / Math.Sqrt(5) + 0.5);

                return actualFibonacciNumber == number;
            }
        }
    }
    return true;
}

The code represents the method that checks whether or not a number is a part of the Fibonacci sequence. Fibonacci sequence is a sequence where each number, starting from zero, is the sum of the previous two numbers.

Numbers 0, 1, and 2 are the first three numbers of that sequence. The method uses nested loops to checks whether the number parameter is one of those numbers. If not, it uses a formula to determine if the number is a part of the sequence.

To quickly remove nesting, we can use the Invert if. Start with the first if statement, position your cursor at the if, and select the Invert if option.

invert if
Visual Studio invert if option

Once we apply the same technique to the other 2 ifs, we get the following code.

public static bool IsFibonnaciNumber(int number)
{
    if (number == 0)
    {
        return true;
    }
    if (number == 1)
    {
        return true;
    }
    if (number == 2)
    {
        return true;
    }

    double fi = (1 + Math.Sqrt(5)) / 2.0;
    int n = (int)Math.Floor(Math.Log(number * Math.Sqrt(5) + 0.5, fi));

    int actualFibonacciNumber = (int)Math.Floor(Math.Pow(fi, n) / Math.Sqrt(5) + 0.5);

    return actualFibonacciNumber == number;
}

Since those if statements return the same output, we could combine them using Consolidate Conditional Expression refactoring.

if (number == 0 || number == 1 || number == 2)
{
    return true;
}

The last optional step is to use the Extract Method refactoring to move that big condition into a separate method.

public class RemoveDuplicatedIfStatements
{
    public static bool IsFibonnaciNumber(int number)
    {
        if (IsNumberWithinFirstThreeNumbers(number))
        {
            return true;
        }

        double fi = (1 + Math.Sqrt(5)) / 2.0;
        int n = (int)Math.Floor(Math.Log(number * Math.Sqrt(5) + 0.5, fi));

        int actualFibonacciNumber = (int)Math.Floor(Math.Pow(fi, n) / Math.Sqrt(5) + 0.5);

        return actualFibonacciNumber == number;
    }

    private static bool IsNumberWithinFirstThreeNumbers(int number)
    {
        return number == 0 || number == 1 || number == 2;
    }
}

Conclusion

The if statement is one of the most valuable tools in any programming language. But you should avoid nesting one if inside another. Nesting can cause a bug in your execution since the nested logic is very hard to understand. The same could be said for a nested loop or a switch statement inside a foreach loop.

And remember, Stack Overflow can’t help you in preventing those issues. But the guard clause can.

Recent Posts