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.
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 statementhrow
.
Additional information about the first four jump statements is
provided in Section 3.5.2.3,
Section 3.5.3.1, and Section 3.5.3.6, later in this chapter. The final
statement, throw
, is discussed in Chapter 9.
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.
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 an
expression (a statement that returns a value) 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. Example 3-7 illustrates.
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(" Setting valueTwo to valueOne value, "); Console.WriteLine("and incrementing ValueOne. "); 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 and 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:
ValueOne was larger. Setting valueTwo to old ValueOne value, and incrementing ValueOne. ValueOne: 31 ValueTwo: 30
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.
If the temperature is higher than 32 degrees, the program should assure you that there is no ice.
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.”
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
this: “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. "); break; case LiberalRepublican: // fall through //Console.WriteLine( //"Liberal Republicans vote Republican "); case Republican: Console.WriteLine("You voted Republican. "); break; case NewLeft: Console.Write("NewLeft is now Progressive"); goto case Progressive; case Progressive: Console.WriteLine("You voted Progressive. "); break; case Libertarian: Console.WriteLine("Libertarians are voting Republican"); goto case Republican; default: Console.WriteLine("You did not pick a valid choice. "); 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.”
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 will execute. You can only “fall through” like
this 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 if the case
statement is
not 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, then 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. ");
If none of the cases matches, the default
case
will be invoked, warning the user of the mistake.
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.
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:
Create a label.
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 dasterdly 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.
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.
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 expression while statement
An expression is any statement that returns a value. An example of
the do...while
loop is shown in Example 3-12.
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.
A careful examination of the
while
loop in Example 3-12 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(" {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 tens (20,30,40, etc.) are called out on the right side of the output.
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
i
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(" {0}", i);
}
}
Console.WriteLine("
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.
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 7.
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 time-stamped 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
");
break;
}
if (signal == "0")
{
// normal traffic condition
// log and continue on
Console.WriteLine("All is well.
");
continue;
}
// Problem. Take action and then log the problem
// and then continue on
Console.WriteLine("{0} -- raise alarm!
",
signal);
}
return 0;
}
}
Output:
Enter a signal: 0
The following signal was received: 0
All is well.
Enter a signal: B
The following signal was received: B
B -- raise alarm!
Enter a signal: A
The following signal was received: A
Faulty processing. 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.
3.143.5.15