C# Control Statements: if, switch, loops & best practices

Control Statements in C#

Ever felt your code is spiraling out of control, like a rollercoaster without brakes? Control statements are your safety rails, keeping the logic on track and making sure your application behaves as expected. If you’ve ever tangled yourself in nested ifs or messy loops, this post is for you.

Let’s demystify control statements in C# by walking through practical examples and best practices you can use right away.

Conditional Statements: if, else if, else

Conditional statements allow your program to make decisions based on conditions at runtime. These constructs are fundamental to controlling the logic flow of your application and are used extensively in nearly every program you write. They allow your application to adapt dynamically and execute different code paths based on user input, data values, or system state.

The most common forms include if, else if, and else statements. As the conditions grow more complex, you can combine them with logical operators, use conditional (ternary) operators for succinctness, or even leverage modern features like pattern matching and null-conditional operators for safer and more readable code.

Let’s dive into each in more detail:

The if Statement

int age = 20;
if (age >= 18)
{
    Console.WriteLine("You are an adult.");
}

Explanation: Checks if age is 18 or above and prints a message. Simple and readable.

The else Statement

if (age >= 18)
{
    Console.WriteLine("You are an adult.");
}
else
{
    Console.WriteLine("You are a minor.");
}

Explanation: Provides an alternative path if the if condition fails.

The else if Statement

if (age < 13)
{
    Console.WriteLine("You are a child.");
}
else if (age < 18)
{
    Console.WriteLine("You are a teenager.");
}
else
{
    Console.WriteLine("You are an adult.");
}

Explanation: Enables multiple condition checks in sequence.

Conditional Operator ?

string status = (age >= 18) ? "Adult" : "Minor";

Explanation: Shorter syntax for a simple if-else. It’s great for assigning values based on a condition.

Using Logical Operators

if (age > 18 && age < 60)
{
    Console.WriteLine("Working age");
}

Explanation: Combine multiple conditions with && (and), || (or), ! (not). Logical operators are powerful for checking compound conditions.

Null-conditional Operators

string name = null;
int? length = name?.Length;

Explanation: Prevents null reference exceptions by safely accessing members only if the object is not null.

Pattern Matching in if (C# 7+)

object obj = 42;
if (obj is int number)
{
    Console.WriteLine($"Number: {number}");
}

Explanation: Declares and casts within the condition. Makes the code cleaner and avoids unsafe casts.

Best Practices

  • Avoid deeply nested if-else blocks.
  • Prefer early exits when possible to reduce nesting.
  • Use pattern matching for cleaner type checks.
  • Favor switch when you are checking the same variable across multiple values.
  • Use null-conditional operators to write safer code and avoid exceptions.

switch Case

The switch statement is another conditional control structure, particularly powerful when you need to compare a single variable against multiple possible values. Unlike if-else, which checks arbitrary conditions, switch simplifies multi-branching logic when you’re checking for equality against constant values.

With enhancements in recent C# versions (7 through 9), switch has grown into a highly expressive and type-safe construct. You can now use pattern matching, tuple comparisons, relational and logical checks, and even expression-based syntax to write clean, declarative code.

Let’s break it down with real examples:

Traditional Syntax

int day = 3;
switch (day)
{
    case 1:
        Console.WriteLine("Monday");
        break;
    case 2:
        Console.WriteLine("Tuesday");
        break;
    default:
        Console.WriteLine("Other day");
        break;
}

Explanation: This classic syntax is readable and effective for fixed value comparisons. Each case must include a break to prevent fall-through.

Pattern Matching (C# 7+)

object val = 5;
switch (val)
{
    case int i:
        Console.WriteLine($"Integer: {i}");
        break;
}

Explanation: Allows checking types and extracting values in one clean expression. This is particularly useful when working with object types or needing runtime type inspection.

Switch Expressions (C# 8+)

string result = day switch
{
    1 => "Monday",
    2 => "Tuesday",
    _ => "Other day"
};

Explanation: A compact and functional-style alternative to traditional switch, especially useful for assignment and return values.

Property Pattern (C# 8+)

Person p = new Person { Age = 25 };
string category = p switch
{
    { Age: < 13 } => "Child",
    { Age: < 20 } => "Teenager",
    _ => "Adult"
};

Explanation: Allows switching based on nested object properties, ideal for decision logic around complex objects.

Tuple Patterns (C# 8+)

(string, int) input = ("Bob", 30);
var output = input switch
{
    ("Bob", 30) => "Matched",
    _ => "Not Matched"
};

Explanation: Pattern match on multiple values simultaneously. Great for handling state combinations or structured data.

Relational Patterns (C# 9+)

int temp = 25;
string state = temp switch
{
    < 0 => "Freezing",
    >= 0 and < 30 => "Cool",
    _ => "Hot"
};

Explanation: Adds comparison-based logic directly inside the switch, avoiding extra if checks.

Logical Patterns (C# 9+)

string label = temp switch
{
    > 0 and < 100 => "Liquid",
    _ => "Unknown"
};

Explanation: Combines logical operators with switch patterns to express complex rules concisely.

Using when Clauses

switch (day)
{
    case int d when d >= 1 && d <= 5:
        Console.WriteLine("Weekday");
        break;
}

Explanation: Refines a case by applying additional conditions. Useful when the same value could map to different behaviors.

Best Practices

  • Prefer switch expressions for readability and conciseness.
  • Use pattern matching to reduce boilerplate and enhance type safety.
  • Always include a default case to handle unexpected input.
  • Group logically related cases to keep your code DRY and readable.

Loops: for, while, do-while, foreach

Loops are the bread and butter of automation in programming. They allow you to repeat code execution without manual duplication, handling everything from array iteration to dynamic user input processing. Whether iterating a fixed number of times, evaluating a condition, or processing collections, C# offers a rich set of loop constructs tailored for each use case.

for Loop

for (int i = 0; i < 5; i++)
{
    Console.WriteLine(i);
}

Explanation: The for loop is ideal when you know exactly how many times you want to execute a block of code. It includes initialization, a condition, and an iteration expression all in one line. This makes it perfect for numeric loops and index-based array operations.

while Loop

int i = 0;
while (i < 5)
{
    Console.WriteLine(i);
    i++;
}

Explanation: Use a while loop when the number of iterations is not known in advance and depends on dynamic conditions. It’s a pre-check loop, meaning it checks the condition before each execution.

do-while Loop

int i = 0;
do
{
    Console.WriteLine(i);
    i++;
} while (i < 5);

Explanation: Unlike while, a do-while loop always executes the code block at least once because the condition is evaluated after the first iteration. Useful when an operation must happen before a condition is evaluated.

foreach Loop

string[] fruits = {"apple", "banana"};
foreach (var fruit in fruits)
{
    Console.WriteLine(fruit);
}

Explanation: Designed for iterating through collections without managing an index. It’s safer and cleaner than for when you just need to access elements.

Infinite Loops

while (true)
{
    // Loop forever unless break or return
    break;
}

Explanation: Used in scenarios like game loops, background services, or event polling where you intentionally keep running until a specific condition or manual break is reached.

Nested Loops

for (int i = 0; i < 3; i++)
{
    for (int j = 0; j < 2; j++)
    {
        Console.WriteLine($"i={i}, j={j}");
    }
}

Explanation: Allows iteration across multi-dimensional data structures. Always monitor performance when nesting loops—complexity grows fast.

Multi-Dimensional Arrays

int[,] matrix = {{1, 2}, {3, 4}};
foreach (var val in matrix)
{
    Console.WriteLine(val);
}

Explanation: Simplifies traversal of 2D arrays. However, if you need to know the indices, you should use nested for loops instead.

The yield Keyword

IEnumerable<int> GetNumbers()
{
    yield return 1;
    yield return 2;
}

Explanation: Lazily returns elements one by one, pausing the state of the method between calls. Great for streaming data or building pipelines.

Parallel Loops

Parallel.For(0, 10, i => Console.WriteLine(i));

Explanation: Executes iterations concurrently using threads. Best used for CPU-intensive operations. Be cautious with shared state and thread safety.

Best Practices & Insights

  • Use for loops for indexed access and defined counts.
  • Favor foreach for readability and collection traversal.
  • Avoid logic that modifies a collection during foreach—it causes exceptions.
  • Use while when the end condition depends on real-time data.
  • Leverage yield for performance when dealing with large or delayed data sources.
  • Evaluate the overhead of Parallel.For before using—it’s powerful but not always efficient.

FAQ

Should I use switch or if-else?

Use switch for cleaner code when evaluating the same variable across multiple values.

Is pattern matching better than type casting?

Yes, it’s safer and more readable.

When should I use yield?

When you want to return a sequence without building an entire list in memory.

Conclusion: Control Your Code Like a Pro

Mastering control statements is like learning to drive stick—clunky at first, smooth later. The better you understand the flow of conditions and loops, the more elegant and efficient your C# code becomes.

Start small. Rewrite a nested if using a switch. Try a pattern match instead of a cast. Experiment with yield. Your future self (and teammates) will thank you.

Have a favorite trick with control statements? Share it in the comments below!

Leave a Reply

Your email address will not be published. Required fields are marked *