Chapter 7
Program Control Statements

What’s in This Chapter

  • The if-else and switch statements
  • The ?: and ?? operators
  • The for, while, do, and foreach loops
  • Enumerators
  • The break and continue statements

Wrox.com Downloads for This Chapter

Please note that all the code examples for this chapter are available as a part of this chapter’s code download on the book’s website at www.wrox.com/go/csharp5programmersref on the Download Code tab.

Program control statements tell an application which other statements to execute under different circumstances. They control the path that execution takes through the code. They include commands that tell the program to execute some statements but not others and to execute certain statements repeatedly.

The two main categories of control statements are decision statements (or conditional statements) and looping statements. The following sections describe in detail the decision and looping statements provided by C#.

Decision Statements

A decisions statement or conditional statement represents a branch in the program. It marks a place where the program can execute one set of statements or another, or possibly no statements at all, depending on some condition. These include several kinds of if statements and switch statements.

if-else Statements

The if-else statement has the following syntax:

if (condition1) statement1;
else if (condition2) statement2;
else if (condition3) statement3;
...
else statementElse;

The conditions are logical expressions that evaluate to either true or false. The statements are the statements that should be executed if the corresponding condition is true.

The program evaluates each of the conditions until it finds one that is true. It then evaluates the corresponding statement and skips all the rest of the series of else and if statements.

If none of the conditions are true, the program executes statementElse.

For example, consider the following code snippet.

string greeting = "";

if (DateTime.Now.DayOfWeek == DayOfWeek.Monday)
    greeting = "Sorry, it's Monday.";
else if (DateTime.Now.DayOfWeek == DayOfWeek.Friday)
    greeting = "Finally, it's Friday!";
else
    greeting = "Welcome to " + DateTime.Now.DayOfWeek.ToString();

MessageBox.Show(greeting);

The code starts by creating the string variable greeting and initializing it to a blank string. It then begins a series of if-else statements.

If it’s Monday, the program sets greeting to "Sorry, it's Monday."

If it’s not Monday and it is Friday, the program sets greeting to "Finally, it's Friday!"

If it’s neither Monday nor Friday, the program sets greeting to "Welcome to" followed by the current day of the week.

After the series of if-else statements, the program displays the greeting in a message box.

The final else section is optional. If you don’t include it and none of the conditions are true, the program doesn’t execute any of the corresponding statements.

All the else if statements are also optional. If the code includes only an if statement, either the program executes the statement that follows or it doesn’t, depending on the value of the condition.

For example, the following code uses a single if statement to display a message box only if it’s Monday. If it’s any other day of the week, the program does nothing.

if (DateTime.Now.DayOfWeek == DayOfWeek.Monday)
    MessageBox.Show("Sorry, it's Monday.");

The “statement” that the program executes when a condition is true can actually be a block of statements surrounded by braces. The following shows the syntax.

if (condition1)
{
statement1;
}
else if (condition2)
{
statement2;
}
else if (condition3)
{
statement3;
}
...
else
{
statementElse;
}

You can place as many statements as you like between the braces.

switch Statements

The switch statement lets a program execute one of several pieces of code depending on a single value. The result is similar to a series of if-else statements that compare a single value to a series of other values.

The basic syntax is as follows:

switch (value)
{
    case expression1:
statements1;
        break;
    case expression2:
        statements2;
        break;
    ...
    «default:
statementsDefault
        break;»
}

Here the program compares value to the expressions until it finds one that matches or it runs out of expressions to test. The expressions must be constant statements and cannot duplicate each other.

If the program finds a match, it executes the corresponding code. If the program runs out of expressions, it executes the statements in the default section (if it is present).

You can place multiple case statements in a group to make them all execute the same code. However, you cannot allow the code from one case section to fall through to the next. If the first section contains lines of code, it must end with a break statement before the next case begins.

The following code is similar to the previous code that checks the day of the week except this version adds extra code for Saturday and Sunday.

string greeting = "";

switch (DateTime.Now.DayOfWeek)
{
    case DayOfWeek.Monday:
        greeting = "Sorry, it's Monday.";
        break;
    case DayOfWeek.Friday:
        greeting = "Finally, it's Friday!";
        break;
    case DayOfWeek.Saturday:
    case DayOfWeek.Sunday:
        greeting = "Yay! The weekend!";
        break;
    default:
        greeting = "Welcome to " + DateTime.Now.DayOfWeek.ToString();
        break;
}

MessageBox.Show(greeting);

This code initializes the greeting variable and then executes a switch statement. Depending on the values of DateTime.Now.DayOfWeek, the program executes the code in the appropriate case section.

The values DayOfWeek.Monday and DayOfWeek.Friday have their own sections. The two values DayOfWeek.Saturday and DayOfWeek.Sunday both execute code that sets greeting to "Yay! The weekend!"

If DateTime.Now.DayOfWeek is not any of those values, the default section sets greeting equal to "Welcome to" followed by the day of the week.

Enumerated Values

The switch statement works naturally with lists of discrete values because each case statement can handle one value.

Enumerated types also work with discrete values, so they work well with switch statements. The enumerated type defines the values and the switch statement uses them, as shown in the following code fragment.

private enum JobStates
{
    Pending,
    Assigned,
    InProgress,
    ReadyToTest,
    Tested,
    Released,
}
private JobStates JobState;
...
switch (JobState)
{
    case JobStates.Pending:
        ...
        break;
    case JobStates.Assigned:
        ...
        break;
    case JobStates.InProgress:
        ...
        break;
    case JobStates.ReadyToTest:
        ...
        break;
    case JobStates.Tested:
        ...
        break;
    case JobStates.Released:
        ...
        break;
}

To catch bugs when changing an enumerated type, many developers include a default section that throws an exception. If you later add a new value to the enumerated type but forget to add corresponding code to the switch statement, the default section throws an exception, so you can fix the code.

For more information on enumerated types, see the section “Enumerations” in Chapter 4.

Conditional and Null-coalescing Operators

The conditional or ternary operator ?: evaluates a boolean expression and returns one of two values, depending on whether the expression is true or false. For example, the following code examines an Employee object’s IsManager property. If IsManager is true, the code sets the employee’s Salary to 90,000. If IsManager is false, the code sets the employee’s Salary to 10,000.

emp.Salary = emp.IsManager ? 90000 : 10000;

This is equivalent to the following if-else statement.

if (emp.IsManager) emp.Salary = 90000;
else emp.Salary = 10000;

The null-coalescing operator ?? examines a reference value. It returns that value if it is not null. It returns its second argument if the first value is null. For example, the following code sets variable orderedBy equal to customer if customer is not null. If customer is null, the code sets orderedBy equal to a new Customer object.

Customer orderedBy = customer ?? new Customer();

This is equivalent to the following if-else statement.

Customer orderedBy;
if (customer != null) orderedBy = customer;
else orderedBy = new Customer();

For more information on the conditional and null-coalescing operators, see the section “Conditional and Null-coalescing Operators” in Chapter 5.

Looping Statements

Looping statements make the program execute a series of statements repeatedly. C# provides four kinds of loops: for loops, while loops, do loops, and foreach loops.

for Loops

A for loop has the following syntax.

for («initialization»; «test»; «increment») statement;

The parts of the loop are as follows:

  • initialization—This piece of code can initialize the loop. Usually it declares and initializes the looping variable; although, you can place other code here as well.
  • test—Each time the program is about to execute the code inside the loop, it evaluates this as a boolean expression. If the result is true, the loop continues. If the result is false, the loop ends. (Note that this means the loop might not execute even once if the condition is false when the loop starts.)
  • increment—After the program has executed the code inside the loop but before it checks test again, it executes this code. Usually this code increments the looping variable.
  • statement—This is the piece of code that is executed repeatedly as long as test is true. You can make the loop include multiple statements by enclosing them in braces.

The following code shows a simple for loop.

for (int i = 1; i <= 10; i++)
{
    Console.WriteLine(i);
}

The initialization part of the for statement declares integer variable i and sets it equal to 1. The test part of the loop determines whether i is less than or equal to 10. The increment part of the loop increases i by 1. Taken together, these pieces of the for statement make the loop run for values i = 1, i = 2, i = 3, ..., i = 10. For each value of i, the program executes the Console.WriteLine statement to display the value of i.

Usually the loop’s initialization piece declares and initializes a single value as in the previous code. If the looping variable has already been declared, you can leave this section blank or only initialize the variable. For example, the following code declares the looping variable i outside of the loop.

int i;
for (i = 1; i < 10; i++)
{
    Console.WriteLine(i);
}

The initialization section can also declare and initialize multiple variables of the same type, and the increment section can execute multiple commands separated by commas. For example, the following code shows a for loop that uses three looping variables: a, b, and c.

for (int a = 0, b = 1, c = 1; a < 1000; a = b, b = c, c = a + b)
{
    Console.WriteLine("a: " + a);
}

In this example, the initialization code declares and initializes the three looping variables, the test section makes the loop run as long as a < 1000, and the increment section updates all three variables.

If you omit the test entirely, the loop runs indefinitely. In that case the loop’s code usually contains a return or other statement to break out of the loop. For example, the following code enters an infinite loop. Each time through the loop, it does something and then asks the user whether it should continue. If the user clicks No, the loop’s return statement makes the program exit the method that contains the loop.

for (; ; )
{
    // Do something ...

    if (MessageBox.Show("Continue?", "Continue?", MessageBoxButtons.YesNo)
        == DialogResult.No) return;
}

Noninteger for Loops

Usually a for loop’s control variable has an integral data type such as a int or long. Because you can do all sorts of strange things inside a for loop’s initialization and increment sections, you aren’t restricted to integer looping variables. For example, the following code uses a float variable to display the values 1.0, 1.5, 2.0, 2.5, and 3.0.

for (float x = 1f; x <= 3f; x += 0.5f)
    Console.WriteLine(x.ToString("0.0"));

Because floating-point numbers cannot exactly represent every possible value, these data types are subject to rounding errors that can lead to unexpected results in for loops. The preceding code works as you would expect, at least on my computer. The following code, however, has problems. Ideally, this code would display values between 1 and 2, incrementing them by 1/7. Because of rounding errors, however, the value of x after seven trips through the loop is approximately 1.85714316. The program adds 1/7 to this and gets 2.00000024. This is greater than the stopping value 2, so the program exits the loop and the Console.WriteLine statement does not execute for x = 2.

for (float x = 1; x <= 2; x += 1f / 7f)
{
    Console.WriteLine(x);
}

One solution to this type of problem is to convert the loop into one that uses an integer control variable. Integer variables don’t have the same problems with rounding errors that floating-point numbers have, so you get more precise control over the values used in the loop.

The following code does roughly the same thing as the previous code. However, this version uses an integer control variable, so this loop executes exactly eight times as desired. The final value printed into the Console window by the program is 2.

float x = 1;
for (int i = 1; i <= 8; i++, x += 1f / 7f)
{
    Console.WriteLine(x);
}

while Loops

A while loop has the following syntax.

while (test)
statement;

As long as the test evaluates to true, the loop executes its statement. Note that this means the loop might not execute even once if the test is false when the loop starts.

You cannot omit the test but you can set it to true to make the while loop repeat indefinitely. If you want to execute more than one statement inside the loop, enclose them in braces. Usually the code inside the loop makes something change so the test eventually evaluates to false and stops the loop.

For a simple example of a while loop, suppose values is an array of integers. The following code uses a while loop to search the array for the value 37.

int index = 0;
while ((index < values.Length) && (values[index] != 37))
    index++;

The code initializes the variable index to 0. Then as long as index is a valid index in the array and the values[index] entry isn’t 37, the loop increments index. (After the loop finishes, if index is equal to the length of the array, the value wasn’t found.)

do Loops

A do loop has the following syntax.

do
statement;
while (test);

A do loop is similar to a while loop except it performs its test after the loop has executed instead of before it executes. That means a do loop always executes at least once.

The following code shows a do loop version of the previous example that searches an array for the value 37.

int index = -1;
do
    index++;
while ((index < values.Length) && (values[index] != 37));

As is the case for a while loop, if you want to execute multiple statements in a do loop, you should enclose them in braces.

foreach Loops

A foreach loop iterates over the items in a collection, array, or other container class that supports foreach loops. A foreach loop has the following syntax.

foreach (variable in group)
statement;

Here, group is a collection, array, or other object that supports foreach. As in for loops, the looping variable must be declared either in or before the foreach statement.

The following code shows a simple foreach loop.

foreach (Employee employee in employees)
{
    Console.WriteLine(employee.Name);
}

Each time through the loop, the program sets the variable employee equal to the next item in the employees array. The code inside the loop displays the employee object’s Name in the Console window.

The looping variable must be of a data type compatible with the objects contained in the group. If the group contains Employee objects, the variable could be an Employee object. It could also be a generic object or any other class that readily converts into an Employee object. For example, if Employee inherits from the Person class, the variable could be of type Person.

C# doesn’t automatically understand what kinds of objects are stored in a collection or array until it tries to use them. If the looping variable’s type is not compatible with a value in the collection, the program throws an InvalidCastException when it tries to assign the looping variable to that value.

That means if a collection or array contains more than one type of object, the looping variable must be of a type that can be converted into all the objects’ types. If the objects in a collection do not inherit from a common ancestor class, the code must use a control variable of type object.

Your code can change the value of the looping variable inside the loop, but that has no effect on the loop’s progress through the collection or array. The loop resets the variable to the next object and continues as if you had never changed the variable’s value. To avoid confusion, don’t bother.

The program cannot modify the collection itself while the loop is executing. For example, if the code is looping over a list of Employee objects, the code inside the loop cannot add or remove objects from the list. (That could be confusing, so this probably wouldn’t be a good idea even if it were allowed.)

One common scenario when dealing with collections is examining every item in the collection and removing some of them. A foreach loop won’t let you remove items while you’re looping over them so that’s not an option.

One approach that seems like it might work (but doesn’t) is to use a for loop, as shown in the following code.

for (int i = 0; i < employees.Count; i++)
{
    if (employees[i].ShouldBeRemoved) employees.RemoveAt(i);
}

Unfortunately, if the code removes an object from the collection, the loop skips the next item because its index has been reduced by one and the loop has already passed that position in the collection.

One solution to this problem is to use a for loop to examine the collection’s objects in reverse order, as shown in the following example.

for (int i = employees.Count - 1; i >= 0; i--)
{
    if (employees[i].ShouldBeRemoved) employees.RemoveAt(i);
}

In this version, the code never needs to use an index after it has been removed because the code is counting backward. The index of an object in the collection also doesn’t change unless that object has already been examined by the loop. The loop examines every item exactly once, no matter which objects are removed.

Enumerators

An enumerator is an object that lets you move through the objects contained by some sort of container class. For example, collections, arrays, and hash tables provide enumerators. This section discusses enumerators for collections, but the same ideas apply for these other classes.

You can use an enumerator to view the objects in a collection but not to modify the collection itself. You can use the enumerator to alter the objects in the collection (for example, to change their properties), but you can generally not use it to add, remove, or rearrange the objects in the collection.

Initially, an enumerator is positioned before the first item in the collection. Your code should use the enumerator’s MoveNext method to step to the next object in the collection. MoveNext returns true if it successfully moves to a new object or false if there are no more objects in the collection.

The Reset method restores the enumerator to its original position before the first object, so you can step through the collection again.

The Current method returns the object that the enumerator is currently reading. Invoking Current throws an exception if the enumerator is not currently reading any object. That happens if the enumerator is before the first object or after the last object.

The following example uses an enumerator to loop through the items in a list named employees.

IEnumerator<Employee> enumerator = employees.GetEnumerator();
while (enumerator.MoveNext())
{
    Console.WriteLine(enumerator.Current.Name);
}

This code creates an enumerator to enumerate over Employee objects. It then enters a while loop. If enumerator.MoveNext returns true, the enumerator has successfully moved to the next object in the collection. As long as it has read an object, the program uses enumerator.Current to get the current object and displays that object’s Name property in the Console window.

When it reaches the end of the employees list, the enumerator’s MoveNext method returns false and the loop ends.

A foreach loop provides roughly the same access to the items in a container class as an enumerator. Under some circumstances, however, an enumerator may provide a more natural way to loop through a container class than a foreach loop. For example, an enumerator can skip several items without examining them closely. You can also use an enumerator’s Reset method to restart the enumeration. To restart a foreach loop, you would need to start the loop over.

The IEnumerable interface defines the features needed for enumerators, so any class that implements the IEnumerable interface provides enumerators. Any class that supports foreach must also implement the IEnumerable interface, so any class that supports foreach also supports enumerators. A few of the classes that implement IEnumerable include the following:

ArrayHybridDictionarySqlDataReader
ArrayListListDictionaryStack
CollectionMessageQueueString
CollectionBaseOdbcDataReaderStringCollection
ControlCollectionOleDbDataReaderStringDictionary
DataViewOracleDataReaderTableCellCollection
DictionaryBaseQueueTableRowCollection
DictionaryEntriesReadOnlyCollectionBaseXmlNode
HashtableSortedListXmlNodeList

Iterators

An iterator is similar in concept to an enumerator. It also provides methods that enable you to step through the items in some sort of container object. Iterators are more specialized than enumerators and work with particular classes.

For example, a GraphicsPath object represents a series of connected lines and curves. A GraphicsPathIterator object can step through the line and curve data contained in a GraphicsPath object.

Iterators are much more specialized than enumerators. How you use them depends on what you need to do and on the kind of iterator, so they are not described in detail here.

break Statements

Each type of loop has a normal termination condition. For example, a while loop continues to execute as long as the test in the while statement is true. When the test becomes false, the loop ends.

In addition to a loop’s standard method for ending, there are a few other ways a program can leave a loop.

One way to break out of a loop early is to execute a return statement. When the program encounters a return statement, the method immediately exits, even if that means breaking out of one or more loops.

The break statement immediately stops the innermost loop containing the statement and passes control to the end of the loop. For example, suppose the Machine class represents the machines in a workshop. The following code searches the array machines for a machine that isn’t busy.

Machine idleMachine = null;
foreach (Machine machine in machines)
{
    if (machine.IsIdle)
    {
        idleMachine = machine;
        break;
    }
}

if (idleMachine != null)
{
    // Assign work to the machine.
}

First, the program declares the variable idleMachine and initializes it to null. It then loops through the Machines array. If it finds an object in the array with IsIdle property true, it saves that machine in the variable idleMachine and uses the break statement to end the loop.

After the loop finishes, the code checks idleMachine to see if it found an idle machine.

A loop can also end early if it encounters an exception and doesn’t include code to handle it. In that case, the loop ends and control passes to an enclosing try catch finally block if there is one. If the loop isn’t enclosed in a try catch finally block, control leaves the executing method and passes up the call stack to the method that called it. If there is an active try catch finally block at that level, it handles the exception. If there is still no try catch finally block, control continues to pass up the call stack until the exception is handled or control pops off the stack, in which case the program crashes. (For more information about try catch finally blocks, see Chapter 9, “Error Handling.”)

continue Statements

The continue statement lets a program skip the rest of its current pass through a loop and start the next pass early.

For example, suppose you need to process a collection of Employee objects. If an employee is not exempt, you need to perform some overtime calculations for that employee. The following code shows how a program could use the continue statement to do this.

foreach (Employee employee in employees)
{
    if (employee.IsExempt) continue;

    // Process the employee.
    ...
}

Inside the loop, the code checks the Employee object’s IsExempt property. If IsExempt is true, the program uses the continue statement to skip the rest of the loop and process the next Employee.

Summary

Control statements form the heart of any program. Decision statements determine what commands are executed, and looping statements determine how many times they are executed.

The if and switch statements are the most commonly used decision statements in C#. The ?: and ?? are less common operators that act as decision statements.

C# provides the for, while, do, and foreach looping statements. Some container classes also support enumerators that let you step through the items in the container. An enumerator provides more flexibility and can sometimes be more natural than a foreach loop.

Using the looping statements described in this chapter, you can perform complex searches over collections of objects. For example, you can loop through an array of numbers to find their maximum, minimum, and average values. The next chapter explains how you can use database-like query syntax to perform similar operations more easily. It tells how you can use LINQ to select, filter, and arrange data taken from lists, collections, arrays, and other data structures.

Exercises

  1. Suppose person is a variable of type Person. The Person class has a Type property of the enumeration type PersonType. The enumeration includes the values Customer, Employee, and Manager. How are the following two blocks of code different?
    // Block 1.
    if (person.Type == PersonType.Customer)
    {
        ...
    }
    else if (person.Type == PersonType.Employee)
    {
        ...
    }
    else if (person.Type == PersonType.Manager)
    {
        ...
    }
    
    // Block 2.
    if (person.Type == PersonType.Customer)
    {
        ...
    }
    if (person.Type == PersonType.Employee)
    {
        ...
    }
    if (person.Type == PersonType.Manager)
    {
        ...
    }
  2. Rewrite the previous code in Block 1 to use a switch statement instead of if statements.
  3. Suppose the Person class has a GetBirthMonth method that looks a person up in a database and returns the person’s birth month as a number between 1 and 12. Your program should use that method to determine the person’s birthstone. Should you use a series of if statements or a switch statement? Why?
  4. Write a series of if statements and a switch statement with roughly the same performance to find birthstones as described in Exercise 3.
  5. Suppose you want to assign a letter grade depending on a student’s test score: 90–100 = A, 80–89 = B, 70–79 = C, 60–69 = D, 0–59 = F. Should you use a switch statement or a series of if statements? Why? Write the code using your chosen approach.
  6. The section “for Loops” in this chapter used the following code as an example.
    for (int a = 0, b = 1, c = 1; a < 1000; a = b, b = c, c = a + b)
    {
        Console.WriteLine("a: " + a);
    }

    This code works but it’s rather confusing because its initialization and increment sections contain so many statements. Rewrite this loop to move the initialization out of the loop and the increment statements inside the loop.

  7. Write a for loop that adds up the numbers in the array values.
  8. Write a while loop that adds up the numbers in the array values.
  9. Write a do loop that adds up the numbers in the array values.
  10. Write a for loop that uses a char looping variable to display the letters A through Z.
  11. The following code sets an overdue Bill object’s Penalty property to $5 or 10 percent of the outstanding balance, whichever is greater.
    bill.Penalty = bill.Status == BillStatus.Overdue ?
        bill.Balance < 50m ? 5m : bill.Balance * 0.1m : 0m;

    Rewrite this code so that it doesn’t use the ?: operator.

  12. Rewrite the following code so that it doesn’t use the ?? operator.
    Student student = GetStudent("Steward Dent") ?? new Student("Steward Dent");
  13. Write a for loop that displays the multiples of 3 between 0 and 100 in largest-to-smallest order.
  14. What values does the following loop display?
    for (int i = 1; i < 100; i += i) Console.WriteLine(i);
  15. Write a program that displays the dates that are Friday the 13ths starting at the beginning of the current year. Display dates one year at a time as long as the user clicks Yes in a dialog asking if the program should continue for the next year.
  16. Rewrite the following foreach loop so that it doesn’t use the continue statement.
    foreach (Employee employee in employees)
    {
        if (employee.IsExempt) continue;
    
        // Process the employee.
        ...
    }

    What advantage does the continue statement have over your solution? When would it be even more useful?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.207.206