C# Programming: From Problem Analysis to Program Design

In C# a complete program instruction is called a statement. Programs consist of sequences of C# statements. Each statement must end with a semicolon (;). For example:

int x; // a statement x = 23; // another statement int y = x; // yet another statement

C# statements are evaluated in order. The compiler starts at the beginning of a statement list and makes its way to the bottom. This would be entirely straightforward, and terribly limiting, were it not for branching. There are two types of branches in a C# program: unconditional branching and conditional branching.

Program flow is also affected by looping and iteration statements, which are signaled by the keywords for, while, do, in, and foreach. Iteration is discussed later in this chapter. For now, let's consider some of the more basic methods of conditional and unconditional branching.

3.5.1 Unconditional Branching Statements

An unconditional branch is created in one of two ways. The first way is by invoking a method. When the compiler encounters the name of a method, it stops execution in the current method and branches to the newly "called" method. When that method returns a value, execution picks up in the original method on the line just below the method call. Example 3-6 illustrates.

Example 3-6. Calling a method

using System; class Functions { static void Main( ) { Console.WriteLine("In Main! Calling SomeMethod( )..."); SomeMethod( ); Console.WriteLine("Back in Main( )."); } static void SomeMethod( ) { Console.WriteLine("Greetings from SomeMethod!"); } } Output: In Main! Calling SomeMethod( )... Greetings from SomeMethod! Back in Main( ).

Program flow begins in Main( ) and proceeds until SomeMethod( ) is invoked (invoking a method is sometimes referred to as "calling" the method). At that point, program flow branches to the method. When the method completes, program flow resumes at the next line after the call to that method.

The second way to create an unconditional branch is with one of the unconditional branch keywords: goto, break, continue, return, or throw. Additional information about the first three jump statements is provided later in this chapter; the final statement, throw, is discussed in Chapter 11.

3.5.2 Conditional Branching Statements

A conditional branch is created by a conditional statement, which is signaled by keywords such as if, else, or switch. A conditional branch occurs only if the condition expression evaluates true.

C and C++ programmers take note: Unlike C and C++, in which any expression can be used in a conditional, C# requires that all conditional expressions evaluate to a Boolean value.

3.5.2.1 If...else statements

If...else statements branch based on a condition. The condition is an expression, tested in the head of the if statement. If the condition evaluates true, the statement (or block of statements) in the body of the if statement is executed.

If statements may contain an optional else statement. The else statement is executed only if the expression in the head of the if statement evaluates false:

if (expression ) statement1 [else statement2 ]

This is the kind of description of the if statement you are likely to find in your compiler documentation. It shows you that the if statement takes a Boolean expression (an expression that evaluates true or false) in parentheses, and executes statement1 if the expression evaluates true. Note that statement1 can actually be a block of statements within braces.

You can also see that the else statement is optional, as it is enclosed in square brackets. Although this gives you the syntax of an if statement, an illustration will make its use clear. See Example 3-7.

Example 3-7. If ... else statements

using System; class Values { static void Main( ) { int valueOne = 10; int valueTwo = 20; if ( valueOne > valueTwo ) { Console.WriteLine( "ValueOne: {0} larger than ValueTwo: {1}", valueOne, valueTwo); } else { Console.WriteLine( "ValueTwo: {0} larger than ValueOne: {1}", valueTwo,valueOne); } valueOne = 30; // set valueOne higher if ( valueOne > valueTwo ) { valueTwo = valueOne++; Console.WriteLine("\nSetting valueTwo to valueOne value, "); Console.WriteLine("and incrementing ValueOne.\n"); Console.WriteLine("ValueOne: {0} ValueTwo: {1}", valueOne, valueTwo); } else { valueOne = valueTwo; Console.WriteLine("Setting them equal. "); Console.WriteLine("ValueOne: {0} ValueTwo: {1}", valueOne, valueTwo); } } }

In Example 3-7, the first if statement tests whether valueOne is greater than valueTwo. The relational operators such as greater than (>), less than (<), and equal to (==) are fairly intuitive to use.

The test of whether valueOne is greater than valueTwo evaluates false (because valueOne is 10 and valueTwo is 20, so valueOne is not greater than valueTwo). The else statement is invoked, printing the statement:

ValueTwo: 20 is larger than ValueOne: 10

The second if statement evaluates true and all the statements in the if block are evaluated, causing two lines to print:

Setting valueTwo to valueOne value, and incrementing ValueOne. ValueOne: 31 ValueTwo: 30

Statement Blocks

You can substitute a statement block anywhere that C# expects a statement. A statement block is a set of statements surrounded by braces.

Thus, where you might write:

if (someCondition) someStatement;

you can instead write:

if(someCondition) { statementOne; statementTwo; statementThree; }

3.5.2.2 Nested if statements

It is possible, and not uncommon, to nest if statements to handle complex conditions. For example, suppose you need to write a program to evaluate the temperature, and specifically to return the following types of information:

  • If the temperature is 32 degrees or lower, the program should warn you about ice on the road.

  • If the temperature is exactly 32 degrees, the program should tell you that there may be ice patches.

There are many good ways to write this program. Example 3-8 illustrates one approach, using nested if statements.

Example 3-8. Nested if statements

using System; class Values { static void Main( ) { int temp = 32; if (temp <= 32) { Console.WriteLine("Warning! Ice on road!"); if (temp == 32) { Console.WriteLine( "Temp exactly freezing, beware of water."); } else { Console.WriteLine("Watch for black ice! Temp: {0}", temp); } } } }

The logic of Example 3-8 is that it tests whether the temperature is less than or equal to 32. If so, it prints a warning:

if (temp <= 32) { Console.WriteLine("Warning! Ice on road!");

The program then checks whether the temp is equal to 32 degrees. If so, it prints one message; if not, the temp must be less than 32 and the program prints the second message. Notice that this second if statement is nested within the first if, so the logic of the else is "since it has been established that the temp is less than or equal to 32, and it isn't equal to 32, it must be less than 32."

All Operators Are Not Created Equal

A closer examination of the second if statement in Example 3-8 reveals a common potential problem. This if statement tests whether the temperature is equal to 32:

if (temp == 32)

In C and C++, there is an inherent danger in this kind of statement. It's not uncommon for novice programmers to use the assignment operator rather than the equals operator, instead creating the statement:

if (temp = 32)

This mistake would be difficult to notice, and the result would be that 32 was assigned to temp, and 32 would be returned as the value of the assignment statement. Because any nonzero value evaluates to true in C and C++, the if statement would return true. The side effect would be that temp would be assigned a value of 32 whether or not it originally had that value. This is a common bug that could easily be overlooked if the developers of C# had not anticipated it!

C# solves this problem by requiring that if statements accept only Boolean values. The 32 returned by the assignment is not Boolean (it is an integer) and, in C#, there is no automatic conversion from 32 to true. Thus, this bug would be caught at compile time, which is a very good thing, and a significant improvement over C++, at the small cost of not allowing implicit conversions from integers to Booleans!

3.5.2.3 Switch statements: an alternative to nested ifs

Nested if statements are hard to read, hard to get right, and hard to debug. When you have a complex set of choices to make, the switch statement is a more powerful alternative. The logic of a switch statement is "pick a matching value and act accordingly."

switch (expression ) { case constant-expression : statement jump-statement [default: statement ] }

As you can see, like an if statement, the expression is put in parentheses in the head of the switch statement. Each case statement then requires a constant expression; that is, a literal or symbolic constant or an enumeration.

If a case is matched, the statement (or block of statements) associated with that case is executed. This must be followed by a jump statement. Typically, the jump statement is break, which transfers execution out of the switch. An alternative is a goto statement, typically used to jump into another case, as illustrated in Example 3-9.

Example 3-9. The switch statement

using System; class Values { static void Main( ) { const int Democrat = 0; const int LiberalRepublican = 1; const int Republican = 2; const int Libertarian = 3; const int NewLeft = 4; const int Progressive = 5; int myChoice = Libertarian; switch (myChoice) { case Democrat: Console.WriteLine("You voted Democratic.\n"); break; case LiberalRepublican: // fall through //Console.WriteLine( //"Liberal Republicans vote Republican\n"); case Republican: Console.WriteLine("You voted Republican.\n"); break; case NewLeft: Console.WriteLine("NewLeft is now Progressive"); goto case Progressive; case Progressive: Console.WriteLine("You voted Progressive.\n"); break; case Libertarian: Console.WriteLine("Libertarians are voting Republican"); goto case Republican; default: Console.WriteLine("You did not pick a valid choice.\n"); break; } Console.WriteLine("Thank you for voting."); } }

In this whimsical example, we create constants for various political parties. We then assign one value (Libertarian) to the variable myChoice and switch on that value. If myChoice is equal to Democrat, we print out a statement. Notice that this case ends with break. break is a jump statement that takes us out of the switch statement and down to the first line after the switch, on which we print "Thank you for voting."

VB6 programmers take note: The equivalent of the C# switch statement is the VB6 Select Case statement. Also, while VB6 allows you to test a range of values using a single Case statement, C# syntax does not provide for this contingency. The following two Case statements are syntactically correct in VB6:

Case Is > 100 Case 50 to 60

However, these statements are not valid in C#. In C#, you can test only a single constant expression. In order to test a range, you must test each value independently and "fall through" to a common case block.

The value LiberalRepublican has no statement under it, and it "falls through" to the next statement: Republican. If the value is LiberalRepublican or Republican, the Republican statements execute. You can only "fall through" in this way if there is no body within the statement. If you uncomment the WriteLine( ) under LiberalRepublican, this program will not compile.

C and C++ programmers take note: you cannot fall through to the next case unless the case statement is empty. Thus, you can write the following:

case 1: // fall through ok case 2:

In this example, case 1 is empty. You cannot, however, write the following:

case 1: TakeSomeAction( ); // fall through not OK case 2:

Here case 1 has a statement in it, and you cannot fall through. If you want case 1 to fall through to case 2, you must explicitly use goto:

case 1: TakeSomeAction( ); goto case 2; // explicit fall through case 2:

If you do need a statement but you then want to execute another case, you can use the goto statement, as shown in the NewLeft case:

goto case Progressive;

It is not required that the goto take you to the case immediately following. In the next instance, the Libertarian choice also has a goto, but this time it jumps all the way back up to the Republican case. Because our value was set to Libertarian, this is just what occurs. We print out the Libertarian statement, go to the Republican case, print that statement, and then hit the break, taking us out of the switch and down to the final statement. The output for all of this is:

Libertarians are voting Republican You voted Republican. Thank you for voting.

Note the default case, excerpted from Example 3-9:

default: Console.WriteLine( "You did not pick a valid choice.\n");

If none of the cases match, the default case will be invoked, warning the user of the mistake.

3.5.2.4 Switch on string statements

In the previous example, the switch value was an integral constant. C# offers the ability to switch on a string, allowing you to write:

case "Libertarian":

If the strings match, the case statement is entered.

3.5.3 Iteration Statements

C# provides an extensive suite of iteration statements, including for, while and do...while loops, as well as foreach loops (new to the C family but familiar to VB programmers). In addition, C# supports the goto, break, continue, and return jump statements.

3.5.3.1 The goto statement

The goto statement is the seed from which all other iteration statements have been germinated. Unfortunately, it is a semolina seed, producer of spaghetti code and endless confusion. Most experienced programmers properly shun the goto statement, but in the interest of completeness, here's how you use it:

  1. Create a label.

  2. goto that label.

The label is an identifier followed by a colon. The goto command is typically tied to a condition, as illustrated in Example 3-10.

Example 3-10. Using goto

using System; public class Tester { public static int Main( ) { int i = 0; repeat: // the label Console.WriteLine("i: {0}",i); i++; if (i < 10) goto repeat; // the dastardly deed return 0; } }

If you were to try to draw the flow of control in a program that makes extensive use of goto statements, the resulting morass of intersecting and overlapping lines looks like a plate of spaghetti; hence the term "spaghetti code." It was this phenomenon that led to the creation of alternatives, such as the while loop. Many programmers feel that using goto in anything other than a trivial example creates confusion and difficult-to-maintain code.

3.5.3.2 The while loop

The semantics of the while loop are "while this condition is true, do this work." The syntax is:

while (expression) statement

As usual, an expression is any statement that returns a value. While statements require an expression that evaluates to a Boolean (true/false) value, and that statement can, of course, be a block of statements. Example 3-11 updates Example 3-10, using a while loop.

Example 3-11. Using a while loop

using System; public class Tester { public static int Main( ) { int i = 0; while (i < 10) { Console.WriteLine("i: {0}",i); i++; } return 0; } }

The code in Example 3-11 produces results identical to the code in Example 3-10, but the logic is a bit clearer. The while statement is nicely self-contained, and it reads like an English sentence: "while i is less than 10, print this message and increment i."

Notice that the while loop tests the value of i before entering the loop. This ensures that the loop will not run if the condition tested is false; thus if i is initialized to 11, the loop will never run.

3.5.3.3 The do...while loop

There are times when a while loop might not serve your purpose. In certain situations, you might want to reverse the semantics from "run while this is true" to the subtly different "do this while this condition remains true." In other words, take the action, and then, after the action is completed, check the condition. For this you will use the do...while loop.

do statement while expression

An expression is any statement that returns a value. Example 3-12 shows the do...while loop.

Example 3-12. The do...while loop

using System; public class Tester { public static int Main( ) { int i = 11; do { Console.WriteLine("i: {0}",i); i++; } while (i < 10); return 0; } }

Here i is initialized to 11 and the while test fails, but only after the body of the loop has run once.

3.5.3.4 The for loop

A careful examination of the while loop in Example 3-11 reveals a pattern often seen in iterative statements: initialize a variable (i = 0), test the variable (i < 10), execute a series of statements, and increment the variable (i++). The for loop allows you to combine all these steps in a single loop statement:

for ([initializers]; [expression]; [iterators]) statement

The for loop is illustrated in Example 3-13.

Example 3-13. The for loop

using System; public class Tester { public static int Main( ) { for (int i=0;i<100;i++) { Console.Write("{0} ", i); if (i%10 == 0) { Console.WriteLine("\t{0}", i); } } return 0; } } Output: 0 0 1 2 3 4 5 6 7 8 9 10 10 11 12 13 14 15 16 17 18 19 20 20 21 22 23 24 25 26 27 28 29 30 30 31 32 33 34 35 36 37 38 39 40 40 41 42 43 44 45 46 47 48 49 50 50 51 52 53 54 55 56 57 58 59 60 60 61 62 63 64 65 66 67 68 69 70 70 71 72 73 74 75 76 77 78 79 80 80 81 82 83 84 85 86 87 88 89 90 90 91 92 93 94 95 96 97 98 99

This for loop makes use of the modulus operator described later in this chapter. The value of i is printed until i is a multiple of 10.

if (i%10 == 0)

A tab is then printed, followed by the value. Thus the 10s (20, 30, 40, etc.) are called out on the right side of the output.

VB6 programmers take note: In C#, looping variables are declared within the header of the for or foreach statement (rather than before the statement begins). This means that they are in scope only within the block, and you cannot refer to them outside the loop. The foreach statement is covered in detail in Chapter 9.

The individual values are printed using Console.Write( ), which is much like WriteLine( ) but which does not enter a newline character, allowing the subsequent writes to occur on the same line.

A few quick points to notice: in a for loop, the condition is tested before the statements are executed. Thus, in the example, i is initialized to zero, then it is tested to see if it is less than 100. Because i < 100 returns true, the statements within the for loop are executed. After the execution, i is incremented (i++).

Note that the variable i is scoped to within the for loop (that is, the variable i is visible only within the for loop). Example 3-14 will not compile.

Example 3-14. Scope of variables declared in a for loop

using System; public class Tester { public static int Main( ) { for (int i=0; i<100; i++) { Console.Write("{0} ", i); if ( i%10 == 0 ) { Console.WriteLine("\t{0}", i); } } Console.WriteLine("\n Final value of i: {0}", i); return 0; } }

The line shown in bold fails, as the variable i is not available outside the scope of the for loop itself.

Whitespace and Braces

There is much controversy about the use of whitespace in programming. For example, this for loop:

for (int i=0;i<100;i++) { if (i%10 == 0) { Console.WriteLine("\t{0}", i); } }

could well be written with more space between the operators:

for ( int i = 0; i < 100; i++ ) { if ( i % 10 == 0 ) { Console.WriteLine("\t{0}", i); } }

Much of this is a matter of personal taste. Although I find whitespace can make code more readable, too much space can cause confusion. In this book, I tend to compress the whitespace to save room on the printed page.

3.5.3.5 The foreach statement

The foreach statement is new to the C family of languages; it is used for looping through the elements of an array or a collection. Discussion of this incredibly useful statement is deferred until Chapter 9.

3.5.3.6 The continue and break statements

There are times when you would like to restart a loop without executing the remaining statements in the loop. The continue statement causes the loop to return to the top and continue executing.

The obverse side of that coin is the ability to break out of a loop and immediately end all further work within the loop. For this purpose the break statement exists.

Break and continue create multiple exit points and make for hard-to-understand, and thus hard-to-maintain, code. Use them with some care.

Example 3-15 illustrates the mechanics of continue and break. This code, suggested to me by one of my technical reviewers, Donald Xie, is intended to create a traffic signal processing system. The signals are simulated by entering numerals and uppercase characters from the keyboard, using Console.ReadLine( ), which reads a line of text from the keyboard.

The algorithm is simple: receipt of a 0 (zero) means normal conditions, and no further action is required except to log the event. (In this case, the program simply writes a message to the console; a real application might enter a timestamped record in a database.) On receipt of an abort signal (here simulated with an uppercase "A"), the problem is logged and the process is ended. Finally, for any other event, an alarm is raised, perhaps notifying the police. (Note that this sample does not actually notify the police, though it does print out a harrowing message to the console.) If the signal is "X," the alarm is raised, but the while loop is also terminated.

Example 3-15. Using continue and break

using System; public class Tester { public static int Main( ) { string signal = "0"; // initialize to neutral while (signal != "X") // X indicates stop { Console.Write("Enter a signal: "); signal = Console.ReadLine( ); // do some work here, no matter what signal you // receive Console.WriteLine("Received: {0}", signal); if (signal == "A") { // faulty - abort signal processing // Log the problem and abort. Console.WriteLine("Fault! Abort\n"); break; } if (signal == "0") { // normal traffic condition // log and continue on Console.WriteLine("All is well.\n"); continue; } // Problem. Take action and then log the problem // and then continue on Console.WriteLine("{0} -- raise alarm!\n", signal); } return 0; } } Output: Enter a signal: 0 Received: 0 All is well. Enter a signal: B Received: B B -- raise alarm! Enter a signal: A Received: A Fault! Abort Press any key to continue

The point of this exercise is that when the A signal is received, the action in the if statement is taken and then the program breaks out of the loop, without raising the alarm. When the signal is 0, it is also undesirable to raise the alarm, so the program continues from the top of the loop.

Категории