Chapter 5

Getting into the Program Flow

IN THIS CHAPTER

Bullet Making decisions if you can

Bullet Deciding what else to do

Bullet Using the while and do … while loops

Bullet Using the for loop and understanding scope

Consider this simple program:

using System;
namespace HelloWorld
{
public class Program
{
// This is where the program starts.
static void Main(string[] args)
{
// Prompt user to enter a name.
Console.WriteLine("Enter your name, please:");

// Now read the name entered.
string name = Console.ReadLine();

// Greet the user with the entered name.
Console.WriteLine("Hello, " + name);

// Wait for user to acknowledge the results.
Console.WriteLine("Press Enter to terminate … ");
Console.Read();
}
}
}

Beyond introducing you to a few fundamentals of C# programming, this program is almost worthless. It simply spits back out whatever you entered. You can imagine more complicated program examples that accept input, perform some type of calculations, generate some type of output (otherwise, why do the calculations?), and then exit at the bottom. However, a program such as this one can be of only limited use.

One key element of any computer processor is its ability to make decisions, which means that the processor sends the flow of execution down one path of instructions if a condition is true or down another path if the condition is false (not true). Any programming language must offer this fundamental capability to control the flow of execution.

The three basic types of flow control are the if statement, the loop, and the jump. (Chapter 6 of this minibook describes the foreach looping control.)

Remember You don't have to type the source code for this chapter manually. In fact, using the downloadable source is a lot easier. You can find the source for this chapter in the CSAIO4D2EBK01CH05 folder of the downloadable source. See the Introduction for details on how to find these source files.

Branching Out with if and switch

The basis of all C# decision-making capability is the if statement:

if (bool-expression)
{
// Control goes here if the expression is true.
}
// Control passes to this statement whether the expression is true or not.

A pair of parentheses immediately following the keyword if contains a conditional expression of type bool. (See Chapter 2 of this minibook for a discussion of bool expressions.) Immediately following the expression is a block of code set off by a pair of braces. If the expression is true, the program executes the code within the braces; if the expression is not true, the program skips the code in the braces. (If the program executes the code in braces, it ends just after the closing brace and continues from there.) The if statement is easier to understand by looking at a concrete example:

// Make sure that a is positive:
// If a is less than 1 …
if (a < 1)
{
// … then assign 1 to it so that it's positive.
a = 1;
}

This segment of code ensures that the variable a is non-negative — greater than or equal to 1. The if statement says, “If a is less than 1, assign 1 to a.” (In other words, turn a into a positive value.)

Technicalstuff The braces aren't required if there is only one statement. C# treats if(bool-expression) statement; as though it had been written if(bool-expression) {statement;}. The general consensus, however, is to always use braces for better clarity. In other words, don't ask — just do it.

Introducing the if statement

Consider a small program that calculates interest. The user enters the principal amount and the interest rate, and the program spits out the resulting value for each year. (This program isn’t sophisticated.) The simplistic calculation appears as follows in C#:

// Calculate the value of the principal plus interest.
decimal interestPaid;
interestPaid = principal * (interest / 100);

// Now calculate the total.
decimal total = principal + interestPaid;

The first equation multiplies the principal principal times the interest interest to produce the interest to be paid — interestPaid. (You divide by 100 because interest is usually calculated by entering a percentage amount.) The interest to be paid is then added back into the principal, resulting in a new principal, which is stored in the variable total.

The program must anticipate almost anything when dealing with human input. For example, you don't want your program to accept a negative principal or interest amount (well, maybe a negative interest). The following CalculateInterest program includes checks to ensure that neither of these entries happens:

static void Main(string[] args)
{
// Prompt user to enter source principal.
Console.Write("Enter principal: ");
string principalInput = Console.ReadLine();
decimal principal = Convert.ToDecimal(principalInput);

// Make sure that the principal is not negative.
if (principal < 0)
{
Console.WriteLine("Principal cannot be negative");
principal = 0;
}

// Enter the interest rate.
Console.Write("Enter interest: ");
string interestInput = Console.ReadLine();
decimal interest = Convert.ToDecimal(interestInput);

// Make sure that the interest is not negative either.
if (interest < 0)
{
Console.WriteLine("Interest cannot be negative");
interest = 0;
}

// Calculate the value of the principal plus interest.
decimal interestPaid = principal * (interest / 100);

// Now calculate the total.
decimal total = principal + interestPaid;

// Output the result.
Console.WriteLine(); // Skip a line.
Console.WriteLine("Principal = " + principal);
Console.WriteLine("Interest = " + interest + "%");
Console.WriteLine();
Console.WriteLine("Interest paid = " + interestPaid);
Console.WriteLine("Total = " + total);
Console.Read();
}

Tip The CalculateInterest program begins by prompting the user for a principle amount using Write() to write a string to the console. Tell the user exactly what you want and, if possible, specify the format. Users don't respond well to uninformative prompts, such as >.

The sample program uses the ReadLine() command to read in whatever the user types; the program returns the value entered, in the form of a string, when the user presses Enter. Because the program is looking for the principal in the form of a decimal, the input string must be converted using the Convert.ToDecimal() command. The result is stored in principalInput.

Remember The ReadLine(), WriteLine(), and ToDecimal() commands are all examples of method calls. A method call delegates some work to another part of the program, called a method. Book 2 describes method calls in detail. For now, don't worry if you have problems understanding precisely how method calls work.

The next line in the example checks principal. If it’s negative, the program outputs a polite message indicating that the input is invalid. The program does the same thing for the interest rate, and then it performs the simplistic interest calculation outlined earlier, in the “Introducing the if statement” section, and spits out the result, using a series of WriteLine() commands. Here's some example output from the program:

Enter principal: 1234
Enter interest: 21

Principal = 1234
Interest = 21%

Interest paid = 259.14
Total = 1493.14

Executing the program with illegal input generates the following output:

Enter principal: 1234
Enter interest: -12.5
Interest cannot be negative

Principal = 1234
Interest = 0%

Interest paid = 0
Total = 1234

Tip Indent the lines within an if statement to enhance readability. This type of indentation is ignored by C# but is helpful to us humans. Most programming editors support autoindenting, whereby the editor automatically indents as soon as you enter the if statement. To set autoindenting in Visual Studio, choose Tools ⇒ Options. Then expand the Text Editor node. From there, expand C#. Finally, click Tabs. On this page, enable Smart Indenting and set the number of spaces per indent to your preference. (The book uses four spaces per indent.) Set the tab size to the same value.

Examining the else statement

Sometimes, your code must check for mutually exclusive conditions. For example, the following code segment stores the maximum of two numbers, a and b, in the variable max:

// Store the maximum of a and b into the variable max.
int max;

// If a is greater than b …
if (a > b)
{
// … save a as the maximum.
max = a;
}

// If a is less than or equal to b …
if (a <= b)
{
// … save b as the maximum.
max = b;
}

The second if statement causes needless processing because the two conditions are mutually exclusive. If a is greater than b, a can't possibly be less than or equal to b. C# defines an else statement for just this case. The else keyword defines a block of code that's executed when the if block isn’t. The code segment to calculate the maximum now appears this way:

// Store the maximum of a and b into the variable max.
int max;

// If a is greater than b …
if (a > b)
{
// … save a as the maximum; otherwise …
max = a;
}
else
{
// … save b as the maximum.
max = b;
}

If a is greater than b, the first block is executed; otherwise, the second block is executed. In the end, max contains the greater of a or b.

Avoiding even the else

Sequences of else clauses can become confusing. Some programmers like to avoid them when doing so doesn't cause even more confusion. You could write the maximum calculation like this:

// Store the maximum of a and b into the variable max.
int max;

// Start by assuming that a is greater than b.
max = a;

// If it is not …
if (b > a)
{
// … then you can change your mind.
max = b;
}

Tip Programmers who like to be cool and cryptic often use the ternary operator, ?:, equivalent to an if/else on one line:

bool informal = true;
string name = informal ? "Chuck" : "Charles"; // Returns "Chuck"

This chunk evaluates the expression before the colon. If the expression is true, return the value after the colon but before the question mark. If the expression is false, return the value after the question mark. This process turns an if…else into an expression. Best practice advises using ternary only rarely because it truly is cryptic.

Technicalstuff C# 9.0 and above offers something called a target typed conditional expression. Normally, the data types of the two outputs of the ternary operator must match. However, in C# 9.0 and above, this requirement changes. Consequently, while this code won't compile in earlier versions of C#, you can use it in C# 9.0 and above.

int x = 2;
Console.WriteLine(x == 1 ? 1 : "Hello");

When x is equal to 1, the output of this conditional expression is the value 1. When x isn’t equal to 1, the output of this conditional expression is the string "Hello". There are all sorts of ways in which to use this new typed conditional expression, including within switch statements. You can discover more about them at https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/target-typed-conditional-expression.

Nesting if statements

The CalculateInterest program warns the user of illegal input; however, continuing with the interest calculation, even if one of the values is illogical, doesn't seem quite right. It causes no real harm here because the interest calculation takes little or no time and the user can ignore the results, but some calculations aren’t nearly as quick. In addition, why ask the user for an interest rate after entering an invalid value for the principal? The user knows that the results of the calculation will be invalid. (You’d be amazed at how much it infuriates users to require input after they know the calculation will fail.) The program should ask the user for an interest rate only if the principal is reasonable and perform the interest calculation only if both values are valid. To accomplish this, you need two if statements, one within the other.

Remember An if statement found within the body of another if statement is an embedded, or nested, statement. The following program, CalculateInterestWithEmbeddedTest, uses embedded if statements to avoid stupid questions if a problem is detected in the input:

static void Main(string[] args)
{
// Define a maximum interest rate.
int maximumInterest = 50;

// Prompt user to enter source principal.
Console.Write("Enter principal: ");
string principalInput = Console.ReadLine();
decimal principal = Convert.ToDecimal(principalInput);

// If the principal is negative …
if (principal < 0)
{
// … generate an error message …
Console.WriteLine("Principal cannot be negative");
}
else // Go here only if principal was > 0: thus valid.
{
// … otherwise, enter the interest rate.
Console.Write("Enter interest: ");
string interestInput = Console.ReadLine();
decimal interest = Convert.ToDecimal(interestInput);

// If the interest is negative or too large …
if (interest < 0 || interest > maximumInterest)
{
// … generate an error message as well.
Console.WriteLine("Interest cannot be negative " +
"or greater than " + maximumInterest);
interest = 0;
}
else // Reach this point only if all is well.
{
// Both the principal and the interest appear to be legal;
// calculate the value of the principal plus interest.
decimal interestPaid;
interestPaid = principal * (interest / 100);

// Now calculate the total.
decimal total = principal + interestPaid;

// Output the result.
Console.WriteLine(); // Skip a line.
Console.WriteLine("Principal = " + principal);
Console.WriteLine("Interest = " + interest + "%");
Console.WriteLine();
Console.WriteLine("Interest paid = " + interestPaid);
Console.WriteLine("Total = " + total);
}
}

Console.Read();
}

The program first reads the principal from the user. If the principal is negative, the program outputs an error message and quits. If the principal is not negative, control passes to the else clause, where the program continues executing.

The interest rate test has been improved in this example. Here, the program requires an interest rate that's non-negative (a mathematical law) and less than a maximum rate (a judiciary law). This if statement uses the following compound test:

if (interest < 0 || interest > maximumInterest)

This statement is true if interest is less than 0 or greater than maximumInterest. Notice the code declares maximumInterest as a variable at the top of the program rather than hard-code it here. Hard-coding refers to using values directly in your code rather than creating a variable to hold them.

Tip Define important constants at the top of your program. Giving a constant a descriptive name (rather than just a number) makes it easy to find and easier to change. If the constant appears ten times in your code, you still have to make only one change to change all references. Entering a correct principal but a negative interest rate generates this output:

Enter principal: 1234
Enter interest: -12.5
Interest cannot be negative or greater than 50.

Only when the user enters both a legal principal and a legal interest rate does the program generate the correct calculation:

Enter principal: 1234
Enter interest: 12.5

Principal = 1234
Interest = 12.5%

Interest paid = 154.250
Total = 1388.250

Running the switchboard

You often want to test a variable for numerous different values. For example, maritalStatus may be 0 for unmarried, 1 for married, 2 for divorced, 3 for widowed, or 4 for none of your business. To differentiate among these values, you could use the following series of if statements:

if (maritalStatus == 0)
{
// Must be unmarried …
// … do something …
}
else
{
if (maritalStatus == 1)
{
// Must be married …
// … do something else …

You can see that these repetitive if statements grow tiresome quickly. Testing for multiple cases is such a common occurrence that C# provides a special construct to decide between a set of mutually exclusive conditions. This control, the switch, works as follows:

switch (maritalStatus)
{
case 0:
// … do the unmarried stuff …
break;
case 1:
// … do the married stuff …
break;
case 2:
// … do the divorced stuff …
break;
case 3:
// … do the widowed stuff …
break;
case 4:
// … get out of my face …
break;
default:
// Goes here if it fails to pass a case;
// this is probably an error condition.
break;
}

The expression at the top of the switch statement is evaluated. In this case, the expression is simply the variable maritalStatus. The value of that expression is then compared against the value of each of the cases. Control passes to the default clause if no match is found. The argument to the switch statement can also be a string:

string s = "Davis";
switch(s)
{
case "Davis":
// … control will actually pass here …
break;
case "Smith":
// … do Smith stuff …
break;
case "Jones":
// … do Jones stuff …
break;
case "Hvidsten":
// … do Hvidsten stuff …
break;
default:
// Goes here if it doesn't pass any cases.
break;
}

Remember Using the switch statement involves these restrictions:

  • The argument to the switch() must be one of the counting types (including char) or a string when using older versions of C#. Starting with C# 7.0, you can use any non-null value.
  • The various case values must refer to values of the same type as the switch expression.
  • The case values must be constant in the sense that their value must be known at compile time. (A statement such as case x isn't legal unless x is a type of constant.)
  • Each clause must end in a break statement (or another exit command, such as return). The break statement passes control out of the switch.

    You can omit a break statement if two cases lead to the same actions: A single case clause may have more than one case label, as in this example:

    string s = "Davis";
    switch(s)
    {
    case "Davis":
    case "Hvidsten":
    // Do the same thing whether s is Davis or Hvidsten
    // since they're related.
    break;
    case "Smith":
    // … do Smith stuff …
    break;
    default:
    // Goes here if it doesn't pass any cases.
    break;
    }

    This approach enables the program to perform the same operation, whether the input is Davis or Hvidsten.

Working with switch expressions

Starting with C# 8.0 and above, you can use a new type of switch called a switch expression. This kind of switch allows you to use expressions as part of a switch statement. An expression is a construction that consists of operators and operands. For example, x > y is an expression. The best way to understand switch expressions is to see one in action, as shown in the SwitchExpression example.

public enum Greetings
{
Morning,
Afternoon,
Evening,
Night
}

public static string GreetString(Greetings value) => value switch
{
Greetings.Morning => "Good Morning!",
Greetings.Afternoon => "Good Afternoon!",
Greetings.Evening => "See you tomorrow!",
Greetings.Night => "Are you still here?",

// Added solely to cover all cases.
_ => "Not sure what time it is!"
};

static void Main(string[] args)
{
Console.WriteLine(GreetString(Greetings.Morning));
Console.WriteLine(GreetString(Greetings.Afternoon));
Console.WriteLine(GreetString(Greetings.Evening));
Console.WriteLine(GreetString(Greetings.Night));
Console.ReadLine();
}

The example begins by creating the Greetings enum (or enumeration). An enumeration is simply a list of value names you want to use. Don't worry about enumerations too much now because you see them demonstrated in Chapter 10 of this minibook. In this case, the enumeration specifies the kinds of greetings that someone can use.

The switch expression comes next. It works similarly to a regular switch in that you start with a variable, value, that you compare to a series of cases. The difference is that value is of type Greetings in this case. When a particular Greetings value matches, the switch expression outputs a string.

Technicalstuff Notice that there is a special value, _, that indicates what the switch should do when none of the other values match. This is a default value. If you don't include the default value, Visual Studio displays a warning message stating that you didn’t cover all contingencies. In this case, you could leave it out because using an enumeration prevents someone from providing an unanticipated value that would require a default value.

Testing the switch expression comes next. The code in Main() tests every combination of Greetings. You see this output:

Good Morning!
Good Afternoon!
See you tomorrow!
Are you still here?

Remember Because this is a C# 8.0 and above feature, you still need to tell the IDE to use a different language version than the default. Make sure to add the following entry to SwitchExpression.csproj.

<PropertyGroup>
<LangVersion>8.0</LangVersion>
</PropertyGroup>

Using pattern matching

C# 9.0 made it possible to extend the switch expression even further by using pattern matching. A pattern can be just about anything that you can recognize as representing a group of related values, even if the values aren’t necessarily contiguous or of the same type. For example, a telephone number is a pattern because you recognize its structure. The SwitchPattern example uses an easier to recognize pattern, those of letters, numbers, and special characters.

public static string LetterType(char letter) => letter switch
{
>= 'a' and <= 'z' => "lowercase letter",
>= 'A' and <= 'Z' => "uppercase letter",
>= '1' and <= '9' => "number",
>= ' ' and <= '/' or
>= ':' and <= '@' or
>= '[' and <= '`' or
>= '{' and <= '~' => "special character",
_ => "Unknown letter type"
};

static void Main(string[] args)
{
Console.WriteLine("a is a " + LetterType('a') + ".");
Console.WriteLine("B is an " + LetterType('B') + ".");
Console.WriteLine("3 is a " + LetterType('3') + ".");
Console.WriteLine("? is a " + LetterType('?') + ".");
Console.WriteLine("À is a " + LetterType('À') + ".");
Console.ReadLine();
}

In this case, the switch uses logical patterns to define letter types based on the content of an input. Notice how you can combine and and or values to create the pattern. The final entry provides a default response in case the input character isn't recognized as part of the pattern. You can see more pattern types supported by C# at https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns.

The testing coding in Main() goes through all the various expected character types. It then provides an uppercase A with a grave accent as input, which isn't part of the pattern. Here’s the output from the example.

a is a lowercase letter.
B is an uppercase letter.
3 is a number.
? is a special character.
A is an Unknown letter type.

Remember Because this is a C# 9.0 and above feature, you still need to tell the IDE to use a different language version than the default. Make sure to add the following entry to SwitchPattern.csproj.

<PropertyGroup>
<LangVersion>9.0</LangVersion>
</PropertyGroup>

Here We Go Loop-the-Loop

The if statement enables a program to take different paths through the code being executed, depending on the results of a bool expression. This statement provides for drastically more interesting programs than programs without decision-making capability. Adding the ability to execute a set of instructions repeatedly adds another quantum jump in capability.

Consider the CalculateInterest program from the section “Introducing the if statement,” earlier in this chapter. Performing this simple interest calculation by using a calculator (or by hand, using a piece of paper) would be much easier than writing and executing a program.

If you could calculate the amount of principal for each of several succeeding years, that would be even more useful. What you need is a way for the computer to execute the same short sequence of instructions multiple times — known as a loop.

Looping for a while

The C# keyword while introduces the most basic form of execution loop:

while (bool-expression)
{
// … repeatedly executed as long as the expression is true.
}

When the while loop is first encountered, the bool expression is evaluated. If the expression is true, the code within the block is executed. When the block of code reaches the closed brace, control returns to the top, and the whole process starts over again. Control passes beyond the closed brace the first time the bool expression is evaluated and turns out to be false.

Remember If the condition is not true the first time the while loop is encountered, the set of commands within the braces is never executed.

Warning It's easy to become confused about how a while loop executes. You may feel that a loop executes until a condition is false, which implies that control passes outside the loop — no matter where the program happens to be executing — as soon as the condition becomes false. This definitely isn’t the case. The program doesn’t check whether the condition is still true until control specifically passes back to the top of the loop.

You can use the while loop to create the CalculateInterestTable program, a looping version of the CalculateInterest program. CalculateInterestTable calculates a table of principals showing accumulated annual payments:

static void Main(string[] args)
{
// Define a maximum interest rate.
int maximumInterest = 50;

// Prompt user to enter source principal.
Console.Write("Enter principal: ");
string principalInput = Console.ReadLine();
decimal principal = Convert.ToDecimal(principalInput);

// If the principal is negative …
if (principal < 0)
{
// … generate an error message …
Console.WriteLine("Principal cannot be negative");
}
else // Go here only if principal was > 0: thus valid.
{
// … otherwise, enter the interest rate.
Console.Write("Enter interest: ");
string interestInput = Console.ReadLine();
decimal interest = Convert.ToDecimal(interestInput);

// If the interest is negative or too large …
if (interest < 0 || interest > maximumInterest)
{
// … generate an error message as well.
Console.WriteLine("Interest cannot be negative " +
"or greater than " + maximumInterest);
interest = 0;
}
else // Reach this point only if all is well.
{
// Both the principal and the interest appear to be
// legal; finally, input the number of years.
Console.Write("Enter number of years: ");
string durationInput = Console.ReadLine();
int duration = Convert.ToInt32(durationInput);

// Verify the input.
Console.WriteLine(); // Skip a line.
Console.WriteLine("Principal = " + principal);
Console.WriteLine("Interest = " + interest + "%");
Console.WriteLine("Duration = " + duration + " years");
Console.WriteLine();

// Now loop through the specified number of years.
int year = 1;
while (year <= duration)
{
// Calculate the value of the principal plus interest.
decimal interestPaid;
interestPaid = principal * (interest / 100);

// Now calculate the new principal by adding
// the interest to the previous principal amount.
principal = principal + interestPaid;

// Round off the principal to the nearest cent.
principal = decimal.Round(principal, 2);

// Output the result.
Console.WriteLine(year + "-" + principal);

// Skip over to next year.
year = year + 1;
}
}
}

Console.Read();
}

The output from a trial run of CalculateInterestTable appears this way:

Enter principal: 10000
Enter interest: 5.5
Enter number of years: 5

Principal = 10000
Interest = 5.5%
Duration = 5 years

1-10550.00
2-11130.25
3-11742.41
4-12388.24
5-13069.59

Each value represents the total principal after the number of years elapsed, assuming simple interest compounded annually. For example, the value of $10,000 at 5.5 percent is $13,069.59 after five years.

The CalculateInterestTable program begins by reading the principal and interest values from the user and checking to make sure that they're valid. CalculateInterestTable then reads the number of years over which to iterate and stores this value in the variable duration.

Before entering the while loop, the program declares a variable year, which it initializes to 1. This will be the “current year” — that is, this number changes “each year” as the program loops. If the year number contained in year is less than the total duration contained in duration, the principal for “this year” is recalculated by calculating the interest based on the “previous year.” The calculated principal is output along with the current-year offset.

Technicalstuff The statement decimal.Round() rounds the calculated value to the nearest fraction of a cent.

The key to the program lies in the last line within the block. The statement year = year + 1; increments year by 1. If year begins with the value 3, its value will be 4 after this expression. This incrementing moves the calculations along from one year to the next.

After the year has been incremented, control returns to the top of the loop, where the value year is compared to the requested duration. In the sample run, if the current year is less than 5, the calculation continues. After being incremented five times, the value of year becomes 6, which is greater than 5, and program control passes to the first statement after the while loop — the program stops looping.

The counting variable year in CalculateInterestTable must be declared and initialized before the while loop in which it is used. In addition, the year variable must be incremented, usually as the last statement within the loop. As this example demonstrates, you have to look ahead to see which variables you need. This pattern is easier to use after you've written a few thousand while loops.

Warning When writing while loops, don't forget to increment the counting variable, as shown in this example:

int nYear = 1;
while (nYear < 10)
{
// … whatever …
}

This example doesn’t increment nYear. Without the increment, nYear is always 1 and the program loops forever. The only way to exit this infinite loop is to terminate the program by pressing Ctrl+C. (So nothing is truly infinite, with the possible exception of a particle passing through the event horizon of a black hole.)

Remember Make sure that the terminating condition can be satisfied. Usually, this means your counting variable is being incremented properly. Otherwise, you're looking at an infinite loop, an angry user, bad press, and 50 years of drought. Infinite loops are a common mistake, so don’t be embarrassed when you get caught in one.

Doing the do … while loop

A variation of the while loop is the do … while loop. In this example, the condition isn't checked until the end of the loop:

int year = 1;
do
{
// … some calculation …
year = year + 1;
} while (year < duration);

In contrast to the while loop, the do … while loop is executed at least once, regardless of the value of duration.

Breaking up is easy to do

You can use two special commands to change how a loop executes: break and continue. Executing the break statement causes control to pass to the first expression immediately following the loop. The similar continue statement passes control straight back up to the conditional expression at the top of the loop to start over and get it right this time.

Suppose that you want to take your money out of the bank as soon as the principal exceeds a certain number of times the original amount, irrespective of the duration in years. You could easily accommodate this amount by adding the following code (in bold) within the loop:

// Now loop through the specified number of years.
int year = 1;
while (year <= duration)
{
// Calculate the value of the principal plus interest.
decimal interestPaid;
interestPaid = principal * (interest / 100);

// Now calculate the new principal by adding
// the interest to the previous principal amount.
principal = principal + interestPaid;

// Round off the principal to the nearest cent.
principal = decimal.Round(principal, 2);

// Output the result.
Console.WriteLine(year + "-" + principal);

// Skip over to next year.
year = year + 1;

// Determine whether we have reached our goal.
if (principal > (maxPower * originalPrincipal))
{
break;
}
}

The break statement isn't executed until the condition within the if comparison is true — in this case, until the calculated principal is maxPower times the original principal or more. Executing the break statement passes control outside the while(year <= duration) statement, and the program resumes execution immediately after the loop.

Looping until you get it right

The CalculateInterestTable program is smart enough to terminate in the event that the user enters an invalid balance or interest amount. However, jumping immediately out of the program just because the user mistypes something seems harsh. A combination of while and break enables the program to be a little more flexible. The CalculateInterestTableMoreForgiving program demonstrates the principle this way:

static void Main(string[] args)
{
// Define a maximum interest rate.
int maximumInterest = 50;

// Prompt user to enter source principal; keep prompting
// until the correct value is entered.
decimal principal;
while (true)
{
Console.Write("Enter principal: ");
string principalInput = Console.ReadLine();
principal = Convert.ToDecimal(principalInput);

// Exit if the value entered is correct.
if (principal >= 0)
{
break;
}

// Generate an error on incorrect input.
Console.WriteLine("Principal cannot be negative");
Console.WriteLine("Try again");
Console.WriteLine();
}

// Now enter the interest rate.
decimal interest;
while (true)
{
Console.Write("Enter interest: ");
string interestInput = Console.ReadLine();
interest = Convert.ToDecimal(interestInput);

// Don't accept interest that is negative or too large …
if (interest >= 0 && interest <= maximumInterest)
{
break;
}

// … generate an error message as well.
Console.WriteLine("Interest cannot be negative " +
"or greater than " + maximumInterest);
Console.WriteLine("Try again");
Console.WriteLine();
}

// Both the principal and the interest appear to be
// legal; finally, input the number of years.
Console.Write("Enter number of years: ");
string durationInput = Console.ReadLine();
int duration = Convert.ToInt32(durationInput);

// Verify the input.
Console.WriteLine(); // Skip a line.
Console.WriteLine("Principal = " + principal);
Console.WriteLine("Interest = " + interest + "%");
Console.WriteLine("Duration = " + duration + " years");
Console.WriteLine();

// Now loop through the specified number of years.
int year = 1;
while (year <= duration)
{
// Calculate the value of the principal plus interest.
decimal interestPaid;
interestPaid = principal * (interest / 100);

// Now calculate the new principal by adding
// the interest to the previous principal.
principal = principal + interestPaid;

// Round off the principal to the nearest cent.
principal = decimal.Round(principal, 2);

// Output the result.
Console.WriteLine(year + "-" + principal);

// Skip over to next year.
year = year + 1;
}

Console.Read();
}

This program works largely the same way as do the examples in previous sections of this chapter, except in the area of user input. This time, a while loop replaces the if statement used in earlier examples to detect invalid input (you would use the same sort of loop to obtain the number of years, but it has been omitted to save space):

decimal principal;
while (true)
{
Console.Write("Enter principal: ");
string principalInput = Console.ReadLine();
principal = Convert.ToDecimal(principalInput);

// Exit when the value entered is correct.
if (principal >= 0)
{
break;
}

// Generate an error on incorrect input.
Console.WriteLine("Principal cannot be negative");
Console.WriteLine("Try again");
Console.WriteLine();
}

This section of code inputs a value from the user within a loop. If the value of the text is okay, the program exits the input loop and continues. However, if the input has an error, the user sees an error message and control passes back to the program flow to start over.

Remember The program continues to loop until the user enters the correct input. (In the worst case, the program could loop until an obtuse user dies of old age.)

Notice that the conditionals have been reversed because the question is no longer whether illegal input should generate an error message, but rather whether the correct input should exit the loop. In the interest section, for example, consider this test:

principal < 0 || principal > maximumInterest

This test changes to this:

interest >= 0 && interest <= maximumInterest

Clearly, interest >= 0 is the opposite of interest < 0. What may not be as obvious is that the OR (||) operator is replaced with an AND (&&) operator. It says, “Exit the loop if the interest is greater than zero AND less than the maximum amount (in other words, if it is correct).” Note that the principal variable must be declared outside the loop because of scope rules, which is explained in the next section.

Warning It may sound obvious, but the expression true evaluates to true. Therefore, while (true) is your archetypical infinite loop. It's the embedded break statement that exits the loop. Therefore, if you use the while (true) loop, make sure that your break condition can occur. The output from a sample execution of this program appears this way:

Enter principal: -1000
Principal cannot be negative
Try again

Enter principal: 1000
Enter interest: -10
Interest cannot be negative or greater than 50
Try again

Enter interest: 10
Enter number of years: 5

Principal = 1000
Interest = 10%
Duration = 5 years

1-1100.0
2-1210.00
3-1331.00
4-1464.10
5-1610.51
Press Enter to terminate …

The program refuses to accept a negative principal or interest amount and patiently explains the mistake on each loop. However, you'd still need to add code to handle situations in which the user entered a blank input or a string value, such as seven.

Warning Explain exactly what the user did wrong before looping back for further input or else that person will become extremely confused. Showing an example may also help, especially for formatting problems. A little diplomacy can’t hurt, either, as Grandma may have pointed out.

Focusing on scope rules

A variable declared within the body of a loop is defined only within that loop. Consider this code snippet:

int days = 1;
while (days < duration)
{
int average = value / days;
// … some series of commands …
days = days + 1;
}

Technicalstuff The variable average isn’t defined outside the while loop. Various reasons for this exist, but consider this one: The first time the loop executes, the program encounters the declaration int average and the variable is defined. On the second loop, the program again encounters the declaration for average, and were it not for scope rules, it would be an error because the variable is already defined. Suffice it to say that the variable average goes away, as far as C# is concerned, as soon as the program reaches the closed brace — and is redefined each time through the loop.

Tip Experienced programmers say that the scope of the variable average is limited to the while loop.

Looping a Specified Number of Times with for

The while loop is the simplest and second most commonly used looping structure in C#. Compared to the for loop, however, the while loop is used about as often as metric tools in an American machine shop. The for loop has this structure:

for (initExpression; condition; incrementExpression)
{
// … body of code …
}

When the for loop is encountered, the program first executes the initExpression expression and then tests the condition. If the condition expression is true, the program executes the body of the loop, which is surrounded by the braces immediately following the for command. When the program reaches the closed brace, control passes to incrementExpression and then back to condition, where the next pass through the loop begins. In fact, the definition of a for loop can be converted into this while loop:

initExpression;
while(condition)
{
// … body of code …
incrementExpression;
}

A for loop example

You can better see how the for loop works in this example:

// Here is one C# expression or another.
a = 1;

// Now loop for awhile.
for (int year = 1; year <= duration; year = year + 1)
{
// … body of code …
}

// The program continues here.
a = 2;

Assume that the program has just executed the a = 1; expression. Next, the program declares the variable year and initializes it to 1. Then the program compares year to duration. If year is less than duration, the body of code within the braces is executed. After encountering the closed brace, the program jumps back to the top and executes the year = year + 1 clause before sliding back over to the year < duration comparison.

Warning The year variable is undefined outside the scope of the for loop. The loop's scope includes the loop’s heading as well as its body.

Why do you need another loop?

Why do you need the for loop if C# has an equivalent while loop? The short answer is that you don't — the for loop doesn’t bring anything to the table that the while loop can't already do.

However, the sections of the for loop exist for convenience — and to clearly establish the three parts that every loop should have: the setup, exit criteria, and increment. Not only is this arrangement easier to read, it’s also easier to get right. (Remember that the most common mistakes in a while loop are forgetting to increment the counting variable and failing to provide the proper exit criteria.) The most important reason to understand the for loop is that it's the loop everyone uses — and it (along with its cousin, foreach) is the one you see 90 percent of the time when you’re reading other people’s code.

Technicalstuff The for loop is designed so that the first expression initializes a counting variable and the last section increments it; however, the C# language doesn't enforce any such rule. You can do anything you want in these two sections; however, you would be ill advised to do anything but initialize and increment the counting variable.

The increment operator is particularly popular when writing for loops. (Chapter 4 of this minibook describes the increment operator along with other operators.) The previous for loop is usually written this way:

for (int year = 1; year <= nDuration; year++)
{
// … body of code …
}

Tip You almost always see the postincrement operator used in a for loop instead of the preincrement operator, although the effect in this case is the same. There's no reason other than habit and the fact that it looks cooler. (The next time you want to break the ice, just haul out your C# listing full of postincrement operators to show how cool you are. It almost never works, but it’s worth a try.)

The for loop has one variation that you may find hard to understand. If the logical condition expression is missing, it’s assumed to be true. Thus for (;;) is an infinite loop. You see for (;;) used as an infinite loop more often than while (true).

Nesting loops

An inner loop can appear within an outer loop, this way:

for ( …some condition …)
{
for ( …some other condition …)
{
// … do whatever …
}
}

The inner loop is executed to completion during each pass through the outer loop. The loop variable (such as year) used in the inner for loop isn't defined outside the inner loop’s scope.

Remember A loop contained within another loop is a nested loop. Nested loops cannot “cross.” For example, the following code won't work:

do // Start a do..while loop.
{
for( …) // Start some for loop.
{
} while( …) // End do..while loop.
} // End for loop.

Remember A break statement within a nested loop breaks out of the inner loop only. In the following example, the break statement exits loop B and goes back into loop A:

// for loop A
for( …some condition …)
{
// for loop B
for( …some other condition …)
{
// … do whatever …
if (something is true)
{
break; // Breaks out of loop B and not loop A
}
}
}

C# doesn't have a break statement that exits both loops simultaneously. You must use two separate break statements, one for each loop.

Technicalstuff Having to use two break statements isn't as big a limitation as it sounds. In practice, the often-complex logic contained within such nested loops is better encapsulated in a method. Executing a return from within any of the loops exits the method, thereby bailing out of all loops, no matter how nested they are. Book 2 Chapter 2 of this minibook describes methods and the return statement.

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

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