Control Flow Statements, Continued

Now that we’ve described Boolean expressions in more detail, we can more clearly describe the control flow statements supported by C#. Many of these statements will be familiar to experienced programmers, so you can skim this section looking for details specific to C#. Note in particular the foreach loop, as it may be new to many programmers.

The while and do/while Loops

Thus far you have learned how to write programs that do something only once. However, computers can easily perform similar operations multiple times. To do so, you need to create an instruction loop. The first instruction loop we discuss is the while loop, because it is the simplest conditional loop. The general form of the while statement is as follows:

while (condition)

   statement

The computer repeatedly executes the statement that is the “body” of the loop as long as the condition (which must be a Boolean expression) evaluates to true. If the condition evaluates to false, code execution skips the body and executes the code following the loop statement. Note that statement continues to execute even if it causes the condition to become false. The loop exits only when the condition is reevaluated “at the top of the loop.” The Fibonacci calculator shown in Listing 4.44 demonstrates the while loop.

Listing 4.44: while Loop Example
1. decimal current;
2. decimal previous;
3. decimal temp;
4. decimal input;
5.  
6. Console.Write("Enter a positive integer:");
7.  
8. // decimal.Parse convert the ReadLine to a decimal
9. // If ReadLine returns null, use "42" as default input
10. input = decimal.Parse(Console.ReadLine() ?? "42");
11.  
12. // Initialize current and previous to 1, the first
13. // two numbers in the Fibonacci series
14. current = previous = 1;
15.  
16. // While the current Fibonacci number in the series is
17. // less than the value input by the user
18. while (current <= input)
19. {
20.     temp = current;
21.     current = previous + current;
22.     previous = temp; // Executes even if previous
23.     // statement caused current to exceed input
24. }
25.  
26. Console.WriteLine(
27.           $"The Fibonacci number following this is { current }");

A Fibonacci number is a member of the Fibonacci series, which includes all numbers that are the sum of the previous two numbers in the series, beginning with 1 and 1. In Listing 4.44, you prompt the user for an integer. Then you use a while loop to find the first Fibonacci number that is greater than the number the user entered.

Beginner Topic
When to Use a while Loop

The remainder of this chapter considers other statements that cause a block of code to execute repeatedly. The term loop body refers to the statement (frequently a code block) that is to be executed within the while statement, since the code is executed in a “loop” until the exit condition is achieved. It is important to understand which loop construct to select. You use a while construct to iterate while the condition evaluates to true. You use a for loop whenever the number of repetitions is known, such as when counting from 0 to n. A do/while loop is similar to a while loop, except that it always executes the loop body at least once.

The do/while loop is very similar to the while loop except that a do/while loop is preferred when the number of repetitions is from 1 to n and n is not known when iterating begins. This pattern frequently occurs when prompting a user for input. Listing 4.45 is taken from the tic-tac-toe program.

Listing 4.45: do/while Loop Example
1. // Repeatedly request player to move until he
2. // enters a valid position on the board
3. bool valid;
4. do
5. {
6.     valid = false;
7.  
8.     // Request a move from the current player
9.     Console.Write(
10.         $"Player {currentPlayer}: Enter move:");
11.     string? input = Console.ReadLine();
12.  
13.     // Check the current player's input
14.     // ...
15.  
16.while(!valid);

In Listing 4.45, you initialize valid to false at the beginning of each iteration, or loop repetition. Next, you prompt and retrieve the number the user input. Although not shown here, you then check whether the input was correct, and if it was, you assign valid equal to true. Since the code uses a do/while statement rather than a while statement, the user is prompted for input at least once.

The general form of the do/while loop is as follows:

do

   statement

while (condition);

As with all the control flow statements, a code block is generally used for the statement in the general form. This of course allows multiple statements to be executed as the loop body. However, C# also allows any single statement except for a labeled statement (see the switch statement later in the chapter) or a local variable declaration.

The for Loop

The for loop iterates a code block until reaching a specified condition. In that way, it is like the while loop. The difference is that the for loop has built-in syntax for initializing, incrementing, and evaluating the value of a counter, known as the loop variable. Because there is a specific location in the loop syntax for an increment operation, the increment and decrement operators are frequently used as part of a for loop.

Listing 4.46 shows the for loop used to display an integer in binary form. The results of this listing appear in Output 4.21.

Listing 4.46: Using the for Loop
1. const int size = 64; // Also available from sizeof(ulong)
2. ulong value;
3. char bit;
4.  
5. Console.Write("Enter an integer: ");
6. // Use long.Parse() to support negative numbers
7. // Assumes unchecked assignment to ulong
8. // If ReadLine returns null, use "42" as default input
9. value = (ulong)long.Parse(Console.ReadLine() ?? "42");
10.  
11. // Set initial mask to 100....
12. ulong mask = 1UL << size - 1;
13. for(int count = 0; count < size; count++)
14. {
15.     bit = ((mask & value) > 0) ? '1' : '0';
16.     Console.Write(bit);
17.     // Shift mask one location over to the right
18.     mask >>= 1;
19. }
Output 4.21
Enter an integer: -42
1111111111111111111111111111111111111111111111111111111111010110

Listing 4.46 performs a bit mask 64 times—once for each bit in the number. The three parts of the for loop header first declare and initialize the variable count, then describe the condition that must be met for the loop body to be executed, and finally describe the operation that updates the loop variable. The general form of the for loop is as follows:

for (initial ; condition ; loop)

   statement

Here is a breakdown of the for loop:

The initial section performs operations that precede the first iteration. In Listing 4.46, it declares and initializes the variable count. The initial expression does not have to be a declaration of a new variable (though it frequently is). It is possible, for example, to declare the variable beforehand and simply initialize it in the for loop, or to skip the initialization section entirely by leaving it blank. Variables declared here are in scope throughout the header and body of the for statement.
The condition portion of the for loop specifies an end condition. The loop exits when this condition is false, exactly like the while loop does. The for loop executes the body only as long as the condition evaluates to true. In Listing 4.46, the loop exits when count is greater than or equal to 64.
The loop expression executes after each iteration. In Listing 4.46, count++ executes after the right shift of the mask (mask >>= 1) but before the condition is evaluated. During the 64th iteration, count is incremented to 64, causing the condition to become false and therefore terminating the loop.
The statement portion of the for loop is the “loop body” code that executes while the conditional expression remains true.

If you wrote out each for loop execution step in pseudocode without using a for loop expression, it would look like this:

1.
Declare and initialize count to 0.
2.
If count is less than 64, continue to step 3; otherwise, go to step 7.
3.
Calculate bit and display it.
4.
Shift the mask.
5.
Increment count by 1.
6.
Jump back to line 2.
7.
Continue the execution of the program after the loop.

The for statement doesn’t require any of the elements in its header. The expression for(;;){ ... } is perfectly valid, although there still needs to be a means to escape from the loop so that it will not continue to execute indefinitely. (If the condition is missing, it is assumed to be the constant true.)

The initial and loop expressions have an unusual syntax to support loops that require multiple loop variables, as shown in Listing 4.47 (with Output 4.22).

Listing 4.47: for Loop Using Multiple Expressions
1. for (int x = 0, y = 5; ((x <= 5) && (y >= 0)); y--, x++)
2. {
3.     Console.Write(
4.         $"{ x }{ ((x > y)  ? '>' : '<')}{ y }\t");
5. }
Output 4.22
0<5    1<4    2<3     3>2    4>1    5>0

Here the initialization clause contains a complex declaration that declares and initializes two loop variables, but this is at least similar to a declaration statement that declares multiple local variables. The loop clause is quite unusual because it can consist of a comma-separated list of expressions, not just a single expression.

Guidelines
CONSIDER refactoring the method to make the control flow easier to understand if you find yourself writing for loops with complex conditionals and multiple loop variables.

The for loop is little more than a more convenient way to write a while loop; you can always rewrite a for loop like this:

{

   initial;

   while (condition)

   {

     statement;

     loop;

   }

}

Guidelines
DO use the for loop when the number of loop iterations is known in advance and the “counter” that gives the number of iterations executed is needed in the loop.
DO use the while loop when the number of loop iterations is not known in advance and a counter is not needed.
The foreach Loop

The last loop statement in the C# language is foreach. The foreach loop iterates through a collection of items, setting a loop variable to represent each item in turn. In the body of the loop, operations may be performed on the item. A nice property of the foreach loop is that every item is iterated over exactly once; it is not possible to accidentally miscount and iterate past the end of the collection, as can happen with other loops.

The general form of the foreach statement is as follows:

foreach(type variable in collection)

   statement

Here is a breakdown of the foreach statement:

type is used to declare the data type of the variable for each item within the collection. It may be var, in which case the compiler infers the type of the item from the type of the collection.
variable is a read-only variable into which the foreach loop automatically assigns the next item within the collection. The scope of the variable is limited to the body of the loop.
collection is an expression, such as an array, representing any number of items.
statement is the loop body that executes for each iteration of the loop.

Consider the foreach loop in the context of the simple example shown in Listing 4.48 with Output 4.23.

Listing 4.48: Determining Remaining Moves Using the foreach Loop
1. // Hardcode initial board as follows
2. // ---+---+---
3. //  1 | 2 | 3
4. // ---+---+---
5. //  4 | 5 | 6
6. // ---+---+---
7. //  7 | 8 | 9
8. // ---+---+---
9. char[] cells = {
10.     '1', '2', '3', '4', '5', '6', '7', '8', '9'
11. };
12.  
13. Console.Write(
14.     "The available moves are as follows: ");
15.  
16. // Write out the initial available moves
17. foreach(char cell in cells)
18. {
19.     if(cell != 'O' && cell != 'X')
20.     {
21.         Console.Write($"{ cell } ");
22.     }
23. }
Output 4.23
The available moves are as follows: 1 2 3 4 5 6 7 8 9

When the execution engine reaches the foreach statement, it assigns to the variable cell the first item in the cells array—in this case, the value '1'. It then executes the code within the block that makes up the foreach loop body. The if statement determines whether the value of cell is 'O' or 'X'. If it is neither, the value of cell is written out to the console. The next iteration then assigns the next array value to cell, and so on.

Note that the compiler prevents modification of the variable (cell) during the execution of a foreach loop.

Beginner Topic
Where the switch Statement Is More Appropriate

Sometimes you might compare the same value in several continuous if statements, as shown with the input variable in Listing 4.49.

Listing 4.49: Checking the Player’s Input with an if Statement
1. // ...
2.  
3. // Check the current player's input
4. if ((input == "1") ||
5.     (input == "2") ||
6.     (input == "3") ||
7.     (input == "4") ||
8.     (input == "5") ||
9.     (input == "6") ||
10.     (input == "7") ||
11.     (input == "8") ||
12.     (input == "9"))
13. {
14.     // Save/move as the player directed
15.     // ...
16.  
17. }
18. else if((input.Length == 0) || (input == "quit"))
19. {
20.     // Retry or quite
21.     // ...
22.  
23. }
24. else
25. {
26.     Console.WriteLine( $"""
27.             ERROR:  Enter a value from 1-9.
28.             Push ENTER to quit
29.             """);
30. }
31.  
32. // ...

This code validates the text entered to ensure that it is a valid tic-tac-toe move. If the value of input were 9, for example, the program would have to perform nine different evaluations. It would be preferable to jump to the correct code after only one evaluation. To enable this, you use a switch statement.

The Basic switch Statement

A basic switch statement is simpler to understand than a complex if statement when you have a value that must be compared against different constant values. The switch statement looks like this:

switch (expression)

{

  case constant:

       statements

   default:

       statements

}

Here is a breakdown of the switch statement:

Guidelines
DO NOT use continue as the jump statement that exits a switch section. This is legal when the switch is inside a loop, but it is easy to become confused about the meaning of break in a later switch section.
expression is the value that is being compared against the different constants. The type of this expression determines the “governing type” of the switch. Allowable governing data types are bool, sbyte, byte, short, ushort, int, uint, long, ulong, char, any enum type (covered in Chapter 9), the corresponding nullable types of each of those value types, and string.
constant is any constant expression compatible with the governing type.
A group of one or more case labels (or the default label) followed by a group of one or more statements is called a switch section. The pattern given previously has two switch sections; Listing 4.50 shows a switch statement with three switch sections.
statements is one or more statements to be executed when the expression equals one of the constant values mentioned in a label in the switch section. The end point of the group of statements must not be reachable. Typically, the last statement is a jump statement such as a break, return, or goto statement.

A switch statement should have at least one switch section; switch(x){} is legal but will generate a warning. Also, the guideline provided earlier was to avoid omitting braces in general. One exception to this rule of thumb is to omit braces for case and break statements because they serve to indicate the beginning and end of a block.

Listing 4.50, with a switch statement, is semantically equivalent to the series of if statements in Listing 4.49.

Listing 4.50: Replacing the if Statement with a switch Statement
1. public static bool ValidateAndMove(
2.     int[] playerPositions, int currentPlayer, string input)
3. {
4.     bool valid = false;
5.  
6.     // Check the current player's input
7.     switch(input)
8.     {
9.         case "1":
10.         case "2":
11.         case "3":
12.         case "4":
13.         case "5":
14.         case "6":
15.         case "7":
16.         case "8":
17.         case "9":
18.             // Save/move as the player directed
19.             // ...
20.             valid = true;
21.             break;
22.         case "":
23.         case "quit":
24.             valid = true;
25.             break;
26.         default:
27.             // If none of the other case statements
28.             // is encountered then the text is invalid
29.             Console.WriteLine(
30.             "ERROR:  Enter a value from 1-9. "
31.             + "Push ENTER to quit");
32.             break;
33.     }
34.  
35.     return valid;
36. }

In Listing 4.50, input is the test expression. Since input is a string, the governing type is string. If the value of input is one of the strings 1, 2, 3, 4, 5, 6, 7, 8, or 9, the move is valid and you change the appropriate cell to match that of the current user’s token (X or O). Once execution encounters a break statement, control leaves the switch statement.

The next switch section describes how to handle the empty string or the string quit; it sets valid to true if input equals either value. The default switch section is executed if no other switch section had a case label that matched the test expression.

Language Contrast: C++—switch Statement Fall-Through

In C++, if a switch section does not end with a jump statement, control “falls through” to the next switch section, executing its code. Because unintended fall-through is a common error in C++, C# does not allow control to accidentally fall through from one switch section to the next. The C# designers believed it was better to prevent this common source of bugs and encourage better code readability than to match the potentially confusing C++ behavior. If you do want one switch section to execute the statements of another switch section, you may do so explicitly with a goto statement, as demonstrated later in this chapter.

There are several things to note about the switch statement:

A switch statement with no switch sections will generate a compiler warning, but the statement will still compile.
Switch sections can appear in any order; the default section does not have to appear last. In fact, the default switch section does not have to appear at all—it is optional.
The C# language requires that every switch section, including the last section, ends with a jump statement (see the next section). This means that switch sections usually end with a break, return, throw, or goto.

C# 7.0 introduced an improvement to the switch statement that enables pattern matching so that any data type—not just the limited few identified earlier—can be used for the switch expression. Pattern matching enables the use of switch statements based on the type of the switch expression and the use of case labels that also declare variables. Lastly, pattern matching switch statements support conditional expressions so that not only the type but also a Boolean expression at the end of the case label can identify which case label should execute. For more information on pattern matching with switch statements and expressions, see Chapter 7.

{{ snackbarMessage }}
;