ZetCode

C# flow control

last modified October 18, 2026

In this article we talk about the flow control in C#. We define several keywords that enable us to control the flow of a C# program.

When a C# program is run, the code is executed from top to bottom. The flow of the program can be altered with various keywords, including if/else, switch, for, while, break, and continue.

The control flow structures can be used to execute code conditionally or multiple times. The programs in this tutorial use top-level statements. Where a random number is needed, the generator has a fixed seed, so that the output is the same on every run.

C# if statement

The if statement has the following general form:

if (expression)
{
    statement;
}

The if keyword is used to check if an expression is true. If it is true, a statement is then executed. The statement can be a single statement or a compound statement. A compound statement consists of multiple statements enclosed in a block. A block is code enclosed in curly brackets.

Program.cs
int n = new Random(5).Next(-5, 5);

Console.WriteLine(n);

if (n > 0)
{
    Console.WriteLine("The n variable is positive");
}

// without braces only the next statement belongs to the if
if (n > 0)
    Console.WriteLine("positive");
Console.WriteLine("this line is always executed");

// conditions combine with &&, || and !
if (n > -3 && n < 3 && n != 0)
{
    Console.WriteLine("small and not zero");
}

if (!(n > 0) || n == 4)
{
    Console.WriteLine("not positive, or four");
}

A random number is generated. If the number is greater than zero, we print a message to the terminal.

int n = new Random(5).Next(-5, 5);

This line generates a random integer in the range -5 to 4. The upper bound of Next is exclusive.

if (n > 0)
{
    Console.WriteLine("The n variable is positive");
}

Using the if keyword, we check if the generated number is greater than zero. The if keyword is followed by a pair of round brackets. Inside the brackets, we place an expression. The expression must result in a boolean value; in C#, an integer is not a condition (unlike in C or C++), so if (n) does not compile. If the value is true, the block enclosed by two curly brackets is executed. In our case, the number is -2, so nothing is printed.

if (n > 0)
    Console.WriteLine("positive");
Console.WriteLine("this line is always executed");

The curly brackets are optional if there is only one statement. This is a source of bugs when a second line is added later with the indentation of the first one; it is safer to always use braces.

if (n > -3 && n < 3 && n != 0)

Conditions are combined with the logical operators && (and), || (or), and ! (not). The operators && and || are short-circuiting: the right operand is evaluated only if the left one does not decide the result. This allows idioms like s != null && s.Length > 0.

$ dotnet run
-2
this line is always executed
small and not zero
not positive, or four

C# else statement

We can use the else keyword to create a simple branch. If the expression following the if keyword evaluates to false, the statement following the else keyword is automatically executed. With else if, we create multiple branches: the condition is tested only if all the previous conditions were not met.

Program.cs
foreach (int n in new[] { -3, 0, 2 })
{
    if (n > 0)
    {
        Console.WriteLine($"{n}: The number is positive");
    }
    else
    {
        Console.WriteLine($"{n}: The number is not positive");
    }
}

Console.WriteLine("--");

foreach (int n in new[] { -3, 0, 2 })
{
    if (n < 0)
    {
        Console.WriteLine($"{n}: The n variable is negative");
    }
    else if (n == 0)
    {
        Console.WriteLine($"{n}: The n variable is zero");
    }
    else
    {
        Console.WriteLine($"{n}: The n variable is positive");
    }
}

Console.WriteLine("--");

// the conditional (ternary) operator for a simple choice between two values
int x = 7;
string parity = x % 2 == 0 ? "even" : "odd";
Console.WriteLine($"{x} is {parity}");

// the null-coalescing operators
string? name = null;
Console.WriteLine(name ?? "anonymous");
name ??= "guest";
Console.WriteLine(name);
Console.WriteLine(name?.Length);

// an early return (guard clause) instead of nesting
Console.WriteLine(Describe(null));
Console.WriteLine(Describe(""));
Console.WriteLine(Describe("falcon"));

static string Describe(string? s)
{
    if (s is null)
    {
        return "no value";
    }

    if (s.Length == 0)
    {
        return "empty";
    }

    return $"text of {s.Length} characters";
}

The first loop takes three numbers and uses if/else. In that program, zero was given to the non-positive numbers, which the second part fixes with else if.

if (n < 0)
{
    ...
}
else if (n == 0)
{
    ...
}
else
{
    ...
}

If the first condition is true, the first block is executed and the remaining two are skipped. Otherwise the second condition is checked, and if that is also false, the else block is executed. The else block is always executed if the previous conditions were not met.

string parity = x % 2 == 0 ? "even" : "odd";

The conditional operator ?: is an expression and chooses one of two values. It suits simple cases; a nested chain of conditional operators is hard to read, and a switch expression is better.

Console.WriteLine(name ?? "anonymous");
name ??= "guest";
Console.WriteLine(name?.Length);

The null-coalescing operator ?? provides a fallback for a null value, ??= assigns it only when the variable is null, and the null-conditional operator ?. accesses a member only if the object is not null.

if (s is null)
{
    return "no value";
}

A guard clause leaves the method early for the special cases. The main path of the method stays at one level of indentation and is easier to read than a pyramid of nested if statements.

$ dotnet run
-3: The number is not positive
0: The number is not positive
2: The number is positive
--
-3: The n variable is negative
0: The n variable is zero
2: The n variable is positive
--
7 is odd
anonymous
guest
5
no value
empty
text of 6 characters

C# switch statement

The switch statement is a selection control flow statement. It allows the value of a variable or an expression to control the flow of program execution via a multi-way branch. It creates multiple branches in a simpler way than a combination of if, else if, and else.

The switch keyword is used to test a value of a variable or an expression against a list of options. The options are written with the case keyword. If a value matches, the statements following the case are executed. The optional default section is executed if no other match is found. Each section must end with break or another statement that leaves it (return, throw, continue, goto); there is no implicit fall-through as in C.

Selecting the day of week

In the following example, we select a day of the week with the switch statement.

Program.cs
var dayOfWeek = new DateTime(2026, 10, 18).DayOfWeek;

switch (dayOfWeek)
{
    case DayOfWeek.Sunday:
        Console.WriteLine("dies Solis");
        break;

    case DayOfWeek.Monday:
        Console.WriteLine("dies Lunae");
        break;

    case DayOfWeek.Tuesday:
        Console.WriteLine("dies Martis");
        break;

    case DayOfWeek.Wednesday:
        Console.WriteLine("dies Mercurii");
        break;

    case DayOfWeek.Thursday:
        Console.WriteLine("dies Jovis");
        break;

    case DayOfWeek.Friday:
        Console.WriteLine("dies Veneris");
        break;

    case DayOfWeek.Saturday:
        Console.WriteLine("dies Saturni");
        break;
}

// several labels share a section; there is no implicit fall-through
switch (dayOfWeek)
{
    case DayOfWeek.Saturday:
    case DayOfWeek.Sunday:
        Console.WriteLine("weekend");
        break;
    default:
        Console.WriteLine("working day");
        break;
}

// goto case continues in another section
int level = 1;
switch (level)
{
    case 1:
        Console.WriteLine("level 1");
        goto case 2;
    case 2:
        Console.WriteLine("level 2");
        break;
}

The example determines the day of the week of a fixed date and prints its Latin equivalent. Then it shows labels that share a section, and goto case.

switch (dayOfWeek)
{
    ...
}

In the round brackets, the switch keyword takes the value that is going to be tested. The body of the switch is placed inside a pair of curly brackets. In the body, we place multiple case labels.

case DayOfWeek.Saturday:
case DayOfWeek.Sunday:
    Console.WriteLine("weekend");
    break;

Several labels can lead to the same statements. This is the only kind of "fall-through" that C# allows. goto case 2; in the last switch jumps to another section on purpose.

$ dotnet run
dies Solis
weekend
level 1
level 2

October 18, 2026 is a Sunday.

Selecting a domain

The input values are tested against a list of options. A case label can be a constant of a string, a number, a character, an enum, or null.

Program.cs
foreach (string input in new[] { " us", "DE", "sk ", "pl", null! })
{
    string? domain = input?.Trim().ToLower();

    switch (domain)
    {
        case "us":
            Console.WriteLine("United States");
            break;

        case "de":
            Console.WriteLine("Germany");
            break;

        case "sk":
            Console.WriteLine("Slovakia");
            break;

        case "hu":
            Console.WriteLine("Hungary");
            break;

        case null:
            Console.WriteLine("No domain");
            break;

        default:
            Console.WriteLine($"Unknown: {domain}");
            break;
    }
}

The program normalizes several domain names and tests the result against a list of options.

string? domain = input?.Trim().ToLower();

The Trim method strips the leading and trailing white space, and ToLower converts the characters to lowercase. Now "us", "US", and " us" are the same domain. The null-conditional operator keeps a null input as null.

case null:
    Console.WriteLine("No domain");
    break;

A null value can be matched by its own case. Without it, the switch on a null string goes to the default section.

default:
    Console.WriteLine($"Unknown: {domain}");
    break;

The default keyword is optional. If none of the case options matches, then the default section is executed.

$ dotnet run
United States
Germany
Slovakia
Unknown: pl
No domain

Patterns and the when clause

Since C# 7, the labels of a switch can be patterns: type patterns, relational patterns (<, >=), logical patterns (and, or, not), and property patterns. A when clause adds an extra condition to a label.

Program.cs
foreach (var age in new[] { -4, 12, 35, 150 })
{
    switch (age)
    {
        case < 0:
            Console.WriteLine("Age cannot be a negative value");
            break;

        case > 130:
            Console.WriteLine("This is an unlikely high age");
            break;

        case >= 18 and < 65:
            Console.WriteLine($"{age}: an adult");
            break;

        case var a when a < 18:
            Console.WriteLine($"{age}: a minor");
            break;

        default:
            Console.WriteLine($"The entered age is {age}");
            break;
    }
}

// type patterns
foreach (object o in new object[] { 42, "text", 3.14, null!, new int[] { 1, 2 }, DateTime.MinValue })
{
    switch (o)
    {
        case int i when i > 40:
            Console.WriteLine($"a big int {i}");
            break;
        case int i:
            Console.WriteLine($"an int {i}");
            break;
        case string { Length: > 3 } s:
            Console.WriteLine($"a long string {s}");
            break;
        case double d:
            Console.WriteLine($"a double {d}");
            break;
        case int[] { Length: 2 } arr:
            Console.WriteLine($"an array with two elements, first {arr[0]}");
            break;
        case null:
            Console.WriteLine("null");
            break;
        default:
            Console.WriteLine($"something else: {o.GetType().Name}");
            break;
    }
}

The example classifies ages with relational patterns and objects with type and property patterns.

case >= 18 and < 65:
    Console.WriteLine($"{age}: an adult");
    break;

case var a when a < 18:
    Console.WriteLine($"{age}: a minor");
    break;

The relational pattern compares with a constant, and and joins two patterns. The var pattern puts the value in a variable, which can then be used in the when clause. The sections are tested from top to bottom, and the first match wins; the compiler reports a section that can never be reached because of a more general one before it.

case string { Length: > 3 } s:
    Console.WriteLine($"a long string {s}");
    break;

A property pattern tests the properties of an object. Here, the object must be a string with a length greater than three, and the variable s holds it.

$ dotnet run
Age cannot be a negative value
12: a minor
35: an adult
This is an unlikely high age
a big int 42
a long string text
a double 3.14
null
an array with two elements, first 1
something else: DateTime

Using an enumeration as the match expression

For the matching expression, we can use any type. In the following example, we use an enumeration.

Program.cs
var color = (Color)new Random(3).Next(0, 7);

switch (color)
{
    case Color.Red:
        Console.WriteLine("The color is red");
        break;

    case Color.Green:
        Console.WriteLine("The color is green");
        break;

    case Color.Blue:
        Console.WriteLine("The color is blue");
        break;

    case Color.Brown:
        Console.WriteLine("The color is brown");
        break;

    case Color.Yellow:
        Console.WriteLine("The color is yellow");
        break;

    case Color.Pink:
        Console.WriteLine("The color is pink");
        break;

    case Color.Orange:
        Console.WriteLine("The color is orange");
        break;

    default:
        Console.WriteLine("The color is unknown.");
        break;
}

Console.WriteLine(color);
Console.WriteLine((int)color);
Console.WriteLine(Enum.IsDefined((Color)42));

// an enum can hold values that have no name, so keep the default section
Console.WriteLine((Color)42);

enum Color { Red, Green, Blue, Brown, Yellow, Pink, Orange }

The example generates a random value of the Color enumeration. The switch statement determines which color was generated.

$ dotnet run
The color is blue
Blue
2
False
42

An enumeration variable can hold any value of its underlying type, not just the named ones; (Color)42 is valid. This is the reason to keep a default section, even if all the names are covered. Enum.IsDefined tests whether a value is a member.

Switch expressions

Switch expressions make the syntax of a switch more concise. They were introduced in C# 8.0. The variable comes before the switch keyword. The case and : elements are replaced with =>, and the default case with the _ discard. The arms are expressions, not statements, and the result is a value.

Program.cs
foreach (var input in new[] { " us", "DE", "sk ", "pl" })
{
    string result = input.Trim().ToLower() switch
    {
        "us" => "United States",
        "de" => "Germany",
        "sk" => "Slovakia",
        "hu" => "Hungary",
        _ => "Unknown"
    };

    Console.WriteLine(result);
}

Console.WriteLine("--");

// relational, logical and property patterns
Console.WriteLine(Grade(95) + Grade(72) + Grade(40));

static string Grade(int points) => points switch
{
    >= 90 => "A",
    >= 70 and < 90 => "B",
    >= 50 and < 70 => "C",
    _ => "F"
};

// tuple patterns combine several values
Console.WriteLine(Rps("rock", "scissors"));
Console.WriteLine(Rps("paper", "paper"));
Console.WriteLine(Rps("rock", "paper"));

static string Rps(string a, string b) => (a, b) switch
{
    ("rock", "scissors") or ("paper", "rock") or ("scissors", "paper") => "first wins",
    ("scissors", "rock") or ("rock", "paper") or ("paper", "scissors") => "second wins",
    _ when a == b => "draw",
    _ => "invalid"
};

// property patterns
var order = new Order("John", 120m, true);
Console.WriteLine(Discount(order));
Console.WriteLine(Discount(order with { Total = 40m }));
Console.WriteLine(Discount(order with { Premium = false }));

static decimal Discount(Order o) => o switch
{
    { Premium: true, Total: > 100 } => 0.15m,
    { Premium: true } => 0.05m,
    { Total: > 100 } => 0.02m,
    _ => 0m
};

// a switch expression must be exhaustive
try
{
    Console.WriteLine(Name(99));
}
catch (System.Runtime.CompilerServices.SwitchExpressionException e)
{
    Console.WriteLine("SwitchExpressionException: " + e.Message);
}

static string Name(int n) => n switch { 1 => "one", 2 => "two" };

record Order(string Customer, decimal Total, bool Premium);

The example uses a switch expression to map the domains, grades, a tuple of two values, and the properties of an object.

static string Grade(int points) => points switch
{
    >= 90 => "A",
    >= 70 and < 90 => "B",
    >= 50 and < 70 => "C",
    _ => "F"
};

Relational and logical patterns express ranges directly. The arms are tested from top to bottom, so the second arm could be simplified to >= 70, since 90 and more were already handled.

("rock", "scissors") or ("paper", "rock") or ("scissors", "paper") => "first wins",

A tuple pattern switches over several values at once, and or combines alternatives.

{ Premium: true, Total: > 100 } => 0.15m,

A property pattern tests properties of the object, and replaces a series of if statements. The first matching arm wins, so the most specific patterns go first.

static string Name(int n) => n switch { 1 => "one", 2 => "two" };

A switch expression must be exhaustive: for each possible input there must be an arm. The compiler warns (CS8509) about this example, since integers other than 1 and 2 are not covered. At run time, an unmatched value throws a SwitchExpressionException. For enums, the warning appears even when all the names are covered, because of values without a name. Add the _ arm or throw an exception on purpose.

$ dotnet run
United States
Germany
Slovakia
Unknown
--
ABF
first wins
draw
second wins
0.15
0.05
0.02
SwitchExpressionException: Non-exhaustive switch expression failed to match its input.
Unmatched value was 99.

C# while statement

The while statement is a control flow statement that allows code to be executed repeatedly based on a given boolean condition.

while (expression)
{
    statement;
}

The while keyword executes the statements inside the block enclosed by the curly brackets. The statements are executed each time the expression evaluates to true. The condition is tested before each cycle, so the body may never run.

Program.cs
int i = 0;
int sum = 0;

while (i < 10)
{
    i++;
    sum += i;
}

Console.WriteLine(sum);

// the condition is tested before every cycle: the body may never run
int k = 5;
while (k < 3)
{
    Console.WriteLine("never printed");
}

// do-while runs at least once
int count = 0;

do
{
    Console.WriteLine($"count is {count}");
    count++;
} while (count < 0);

// a loop that reads until a sentinel
var queue = new Queue<int>([4, 8, 15, 16, 0, 23, 42]);
int total = 0;

while (queue.TryDequeue(out int value) && value != 0)
{
    total += value;
}

Console.WriteLine($"total before the zero: {total}");

In the code example, we calculate the sum of the numbers 1 to 10, show a loop that never runs, a do/while loop, and a loop that ends at a marker.

A loop has three parts: initialization, testing, and updating. Each execution of the body is called a cycle.

int i = 0;

while (i < 10)
{
    i++;
    sum += i;
}

We initialize the i variable, which is used as a counter. The expression in the round brackets is the second phase, the testing. The statements in the body are executed until the expression evaluates to false. Incrementing the counter is the last phase, the updating. Note that improper handling of the while loops may lead to endless cycles.

do
{
    Console.WriteLine($"count is {count}");
    count++;
} while (count < 0);

The do/while statement runs the body at least once, even if the condition is not met, because the condition is tested after the body.

while (queue.TryDequeue(out int value) && value != 0)

A loop can read values until a marker: this loop takes items from a queue until the queue is empty or a zero is found. The side-effect in the condition is a common idiom with the TryXxx methods.

$ dotnet run
55
count is 0
total before the zero: 43

C# for statement

When the number of cycles is known before the loop starts, we can use the for statement. In this construct we declare a counter variable which is changed in each repetition of the loop. A for loop has three parts in the header: the initialization, the condition, and the iteration.

Program.cs
for (int i = 0; i < 10; i++)
{
    Console.Write($"{i} ");
}

Console.WriteLine();

string[] planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"];

for (int i = 0; i < planets.Length; i++)
{
    Console.WriteLine($"{i + 1}. {planets[i]}");
}

Console.WriteLine("In reverse:");

for (int i = planets.Length - 1; i >= 0; i--)
{
    Console.Write($"{planets[i]} ");
}

Console.WriteLine();

// a step other than one
for (int i = 0; i <= 20; i += 5)
{
    Console.Write($"{i} ");
}

Console.WriteLine();

// every part of the header is optional
int n = 0;
for (; ; )
{
    if (++n == 3) break;
}

Console.WriteLine(n);

In this example, we print the numbers 0 to 9, traverse an array in both directions, use a different step, and write a loop with an empty header.

for (int i = 0; i < 10; i++)
{
    Console.Write($"{i} ");
}

There are three phases. In the first phase, we initialize the counter i to zero; this is done only once. Next comes the condition. If the condition is met, the statements in the block are executed. In the third phase, the counter is increased. Then we repeat phases 2 and 3 until the condition is not met and the loop is left.

for (int i = 0; i < planets.Length; i++)
{
    Console.WriteLine($"{i + 1}. {planets[i]}");
}

A for loop is used for the traversal of an array. Arrays are accessed by zero-based indexes, so the counter starts from zero and the condition checks if the counter is less than the Length of the array. The loop that counts down starts from Length - 1, the index of the last element, and ends at zero.

for (; ; )
{
    if (++n == 3) break;
}

All three parts of the header are optional. Without a condition, the loop runs until a break.

$ dotnet run
0 1 2 3 4 5 6 7 8 9
1. Mercury
2. Venus
3. Earth
4. Mars
5. Jupiter
6. Saturn
7. Uranus
8. Neptune
In reverse:
Neptune Uranus Saturn Jupiter Mars Earth Venus Mercury
0 5 10 15 20
3

More expressions in the for loop

More expressions can be placed in the initialization and iteration part of the for loop, separated by commas.

Program.cs
var r = new Random(1);
var values = new int[10];

int sum = 0;
int num = 0;

for (int i = 0; i < 10; i++, sum += num)
{
    num = r.Next(10);
    values[i] = num;
}

Console.WriteLine(string.Join(",", values));
Console.WriteLine("The sum of the values is {0}", sum);
Console.WriteLine(values.Sum() == sum);

// two variables in one loop
for (int lo = 0, hi = 9; lo < hi; lo++, hi--)
{
    (values[lo], values[hi]) = (values[hi], values[lo]);
}

Console.WriteLine(string.Join(",", values));

// the variable of a for loop is captured by a lambda; each iteration has its own variable in foreach
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
    actions.Add(() => Console.Write($"{i} "));
}

actions.ForEach(a => a());
Console.WriteLine();

var actions2 = new List<Action>();
foreach (int i in Enumerable.Range(0, 3))
{
    actions2.Add(() => Console.Write($"{i} "));
}

actions2.ForEach(a => a());
Console.WriteLine();

We create an array of ten pseudo-random numbers and calculate the sum of the numbers. Then we reverse the array with two counters, and look at how closures capture loop variables.

for (int i = 0; i < 10; i++, sum += num)

In the third part of the loop, we have two expressions separated by a comma. The counter is incremented and the current number is added to the sum variable. This is clever but obscure; in real code, the sum would be updated in the body.

for (int lo = 0, hi = 9; lo < hi; lo++, hi--)
{
    (values[lo], values[hi]) = (values[hi], values[lo]);
}

Two counters move from both ends toward the middle, and a tuple assignment swaps the elements.

for (int i = 0; i < 3; i++)
{
    actions.Add(() => Console.Write($"{i} "));
}

A famous trap: the variable of a for loop is one variable shared by all cycles. The lambdas capture the variable, not its value at the time, so all three print 3 when called after the loop. In a foreach loop, each cycle has its own variable, and the result is 0 1 2.

$ dotnet run
2,1,4,7,6,4,3,9,1,6
The sum of the values is 43
True
6,1,9,3,4,6,7,4,1,2
3 3 3
0 1 2

Nested for loops

Loops can be nested: a loop can be placed inside another loop. All the cycles of the nested loop are executed for each cycle of the outer loop.

Program.cs
string[] a1 = ["A", "B", "C"];
string[] a2 = ["A", "B", "C"];

for (int i = 0; i < a1.Length; i++)
{
    for (int j = 0; j < a2.Length; j++)
    {
        Console.Write(a1[i] + a2[j] + " ");
    }

    Console.WriteLine();
}

// a multiplication table
for (int i = 1; i <= 4; i++)
{
    for (int j = 1; j <= 5; j++)
    {
        Console.Write($"{i * j,4}");
    }

    Console.WriteLine();
}

// a triangle
for (int i = 1; i <= 4; i++)
{
    Console.WriteLine(new string('*', i));
}

In this example, we create a Cartesian product of two arrays, a multiplication table, and a triangle.

for (int i = 0; i < a1.Length; i++)
{
    for (int j = 0; j < a2.Length; j++)
    {
        Console.Write(a1[i] + a2[j] + " ");
    }

    Console.WriteLine();
}

A Cartesian product pairs each element of one array with all elements of the other. The inner loop is executed fully for each of the cycles of the outer loop. The cost of nested loops multiplies: two loops over n elements do n×n cycles.

$ dotnet run
AA AB AC
BA BB BC
CA CB CC
   1   2   3   4   5
   2   4   6   8  10
   3   6   9  12  15
   4   8  12  16  20
*
**
***
****

C# foreach statement

The foreach construct simplifies traversing collections of data. It has no explicit counter. The statement goes through the collection one element at a time, and the current element is copied to a variable defined in the construct. See the foreach tutorial for details.

Program.cs
string[] planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"];

foreach (string planet in planets)
{
    Console.Write($"{planet} ");
}

Console.WriteLine();

// the same with a list and a dictionary
var moons = new Dictionary<string, int> { ["Earth"] = 1, ["Mars"] = 2, ["Jupiter"] = 95 };

foreach (var (planet, count) in moons)
{
    Console.WriteLine($"{planet} has {count} moon(s)");
}

In this example, we use the foreach statement to go through an array of planets and a dictionary.

foreach (string planet in planets)
{
    Console.Write($"{planet} ");
}

The planets is the array that we iterate through. The planet is a temporary, read-only variable that has the current value.

$ dotnet run
Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune
Earth has 1 moon(s)
Mars has 2 moon(s)
Jupiter has 95 moon(s)

C# break statement

The break statement terminates the innermost loop (while, do, for, foreach) or switch that contains it.

C# continue statement

The continue statement skips the rest of the current cycle of a loop and continues with the next iteration.

Program.cs
var random = new Random(7);

while (true)
{
    int num = random.Next(1, 30);
    Console.Write("{0} ", num);

    if (num == 22)
    {
        break;
    }
}

Console.WriteLine();

// continue skips the rest of the cycle
int n = 0;

while (n < 20)
{
    n++;

    if (n % 2 == 0)
    {
        continue;
    }

    Console.Write($"{n} ");
}

Console.WriteLine();

// break leaves only the innermost loop; to leave both we use a flag, a method with return, or goto
bool found = false;
for (int i = 1; i < 10 && !found; i++)
{
    for (int j = 1; j < 10; j++)
    {
        if (i * j == 42)
        {
            Console.WriteLine($"{i} x {j} = 42");
            found = true;
            break;
        }
    }
}

Console.WriteLine(FindProduct(42));

static string FindProduct(int target)
{
    for (int i = 1; i < 10; i++)
    {
        for (int j = 1; j < 10; j++)
        {
            if (i * j == target)
            {
                return $"{i} x {j} = {target}";
            }
        }
    }

    return "not found";
}

for (int i = 1; i < 10; i++)
{
    for (int j = 1; j < 10; j++)
    {
        if (i * j == 56)
        {
            Console.WriteLine($"goto: {i} x {j}");
            goto done;
        }
    }
}

done:
Console.WriteLine("finished");

The example draws random numbers until it gets 22, prints the odd numbers below 20, and then searches a product with nested loops in three ways.

while (true)
{
    int num = random.Next(1, 30);
    Console.Write("{0} ", num);

    if (num == 22)
    {
        break;
    }
}

We define an endless while loop and use the break statement to get out of it. We choose a random value from 1 to 29, print it, and when it is 22, we finish the loop.

if (n % 2 == 0)
{
    continue;
}

If the expression n % 2 is 0, the number is even. The continue statement skips the rest of the cycle, so the number is not printed, and the next cycle starts.

bool found = false;
for (int i = 1; i < 10 && !found; i++)

In nested loops, break leaves only the inner loop. To leave both, we can use a flag in the condition of the outer loop, move the loops into a method and use return, which is usually the cleanest, or use goto to a label after the loops. goto is rarely used, but leaving deeply nested loops is one of the situations where it is accepted.

$ dotnet run
12 26 20 2 11 20 2 28 25 25 14 28 13 4 26 12 24 10 26 1 22
1 3 5 7 9 11 13 15 17 19
6 x 7 = 42
6 x 7 = 42
goto: 7 x 8
finished

Exceptions and return

Exceptions and return change the flow too. The try statement runs code that may fail; catch blocks handle the errors, and the finally block runs always. return ends the method, and throw ends it with an error.

Program.cs
// exceptions are a form of flow control, too
foreach (var text in new[] { "42", "x", null })
{
    try
    {
        int n = int.Parse(text!);
        Console.WriteLine($"parsed {n}");
    }
    catch (FormatException)
    {
        Console.WriteLine($"'{text}' is not a number");
    }
    catch (ArgumentNullException)
    {
        Console.WriteLine("null given");
    }
    finally
    {
        Console.WriteLine("-- done");
    }
}

// when an error is expected, test instead of catching: it is faster and clearer
foreach (var text in new[] { "42", "x" })
{
    Console.WriteLine(int.TryParse(text, out int n) ? $"parsed {n}" : $"'{text}' is not a number");
}

// return ends the current method, throw leaves it with an exception
Console.WriteLine(SafeDivide(10, 2));
Console.WriteLine(SafeDivide(1, 0));

static string SafeDivide(int a, int b)
{
    if (b == 0)
    {
        return "cannot divide by zero";
    }

    return (a / b).ToString();
}

The program parses three inputs and handles each failure separately.

foreach (var text in new[] { "42", "x" })
{
    Console.WriteLine(int.TryParse(text, out int n) ? $"parsed {n}" : $"'{text}' is not a number");
}

Exceptions are for exceptional situations. When an error is expected, as with user input, test with TryParse instead of catching an exception; it is clearer and much faster, since throwing an exception is expensive.

$ dotnet run
parsed 42
-- done
'x' is not a number
-- done
null given
-- done
parsed 42
'x' is not a number
5
cannot divide by zero

Source

Selection statements and iteration statements

In this article we have covered C# control flow structures.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.