Was this page helpful?
Your feedback about this content is important. Let us know what you think.
Additional feedback?
1500 characters remaining
Export (0) Print
Expand All

Chapter 4: Flow Control

Applies to: Visual Studio 2010

Published: April, 2010

Provided by: Karli Watson

Referenced Image

Buy This Book from Publisher

This topic contains the following sections.

  • Boolean logic and how to use it

  • How to branch code

  • How to loop code

All of the C# code you've seen so far has had one thing in common. In each case, program execution has proceeded from one line to the next in top-to-bottom order, missing nothing. If all applications worked like this, then you would be very limited in what you could do. This chapter describes two methods for controlling program flow—that is, the order of execution of lines of C# code; branching and looping. Branching executes code conditionally, depending on the outcome of an evaluation, such as ″only execute this code if the variable myVal is less than 10.″ Looping repeatedly executes the same statements, either a certain number of times or until a test condition has been reached.

Both of these techniques involve the use of Boolean logic. In the last chapter you saw the bool type, but didn't actually do much with it. This chapter uses it a lot, so the chapter begins by discussing what is meant by Boolean logic so that you can use it in flow control scenarios.

The bool type introduced in the previous chapter can hold one of only two values: true or false. This type is often used to record the result of some operation, so that you can act on this result. In particular, bool types are used to store the result of a comparison.

NoteNote

As a historical aside, it is the work of the mid-nineteenth-century English mathematician George Boole that forms the basis of Boolean logic.

For instance, consider the situation (mentioned in the chapter introduction) in which you want to execute code based on whether a variable, myVal, is less than 10. To do this, you need some indication of whether the statement ″myVal is less than 10″ is true or false—that is, you need to know the Boolean result of a comparison.

Boolean comparisons require the use of Boolean comparison operators (also known as relational operators), which are shown in the following table. In all cases here, var1 is a bool type variable, whereas the types of var2 and var3 may vary.

Operator

Category

Example Expression

Result

==

Binary

var1 = var2 == var3;

var1 is assigned the value true if var2 is equal to var3, or false otherwise.

!=

Binary

var1 = var2 != var3;

var1 is assigned the value true if var2 is not equal to var3, or false otherwise.

<

Binary

var1 = var2 < var3;

var1 is assigned the value true if var2 is less than var3, or false otherwise.

>

Binary

var1 = var2 > var3;

var1 is assigned the value true if var2 is greater than var3, or false otherwise.

<=

Binary

var1 = var2 <= var3;

var1 is assigned the value true if var2 is less than or equal to var3, or false otherwise.

>=

Binary

var1 = var2 >= var3;

var1 is assigned the value true if var2 is greater than or equal to var3, or false otherwise.

You might use operators such as these on numeric values in code:

bool isLessThan10;
isLessThan10 = myVal < 10;

This code results in isLessThan10 being assigned the value true if myVal stores a value less than 10, or false otherwise.

You can also use these comparison operators on other types, such as strings:

bool isKarli;
isKarli = myString == ″Karli″;

Here, isKarli is true only if myString stores the string ″Karli″.

You can also compare variables with Boolean values:

bool isTrue;
isTrue = myBool == true;

Here, however, you are limited to the use of the == and != operators.

NoteNote

A common code error occurs if you unintentionally assume that because val1 < val2 is false, val1 > val2 is true. If val1 == val2, then both these statements are false.

Some other Boolean operators are intended specifically for working with Boolean values, as shown in the following table:

Operator

Category

Example Expression

Result

!

Unary

var1 = !var2;

var1 is assigned the value true if var2 is false, or false if var2 is true. (Logical NOT)

&

Binary

var1 = var2 & var3;

var1 is assigned the value true if var2 and var3 are both true, or false otherwise. (Logical AND)

|

Binary

var1 = var2 | var3;

var1 is assigned the value true if either var2 or var3 (or both) is true, or false otherwise. (Logical OR)

Binary

var1 = var2 ∧ var3;

var1 is assigned the value true if either var2 or var3, but not both, is true, or false otherwise. (Logical XOR or exclusive OR)

Therefore, the previous code snippet could also be expressed as follows:

bool isTrue;
isTrue = myBool & true;

The & and | operators also have two similar operators, known as conditional Boolean operators, shown in the following table:

Operator

Category

Example Expression

Result

&&

Binary

var1 = var2 && var3;

var1 is assigned the value true if var2 and var3 are both true, or false otherwise. (Logical AND)

Binary

var1 = var2 ‖ var3;

var1 is assigned the value true if either var2 or var3 (or both) is true, or false otherwise. (Logical OR)

The result of these operators is exactly the same as & and |, but there is an important difference in the way this result is obtained, which can result in better performance. Both of these look at the value of their first operands (var2 in the preceding table) and, based on the value of this operand, may not need to process the second operands (var3 in the preceding table) at all.

If the value of the first operand of the && operator is false, then there is no need to consider the value of the second operand, because the result will be false regardless. Similarly, the operator returns true if its first operand is true, regardless of the value of the second operand. This isn't the case for the & and | operators shown earlier. With these, both operands are always evaluated.

Because of this conditional evaluation of operands, you get a small performance increase if you use && and instead of & and |. This is particularly apparent in applications that use these operators a lot. As a rule of thumb, always use && and where possible. These operators really come into their own in more complicated situations, where computation of the second operand is possible only with certain values of the first operand, as shown in this example:

var1 = (var2 != 0) && (var3 / var2 > 2);

Here, if var2 is zero, then dividing var3 by var2 results in either a ″division by zero″ error or var1 being defined as infinite (the latter is possible, and detectable, with some types, such as float).

NoteNote

At this point, you may be asking why the & and | operators exist at all. The reason is that these operators may be used to perform operations on numeric values. In fact, as you will see shortly in the section ″Bitwise Operators,″ they operate on the series of bits stored in a variable, rather than the value of the variable.

Boolean Assignment Operators

Boolean comparisons can be combined with assignments by using Boolean assignment operators. These work in the same way as the mathematical assignment operators that were introduced in the preceding chapter (+=, *=, and so on). The Boolean versions are shown in the following table:

Operator

Category

Example Expression

Result

&=

Binary

var1 &= var2;

var1 is assigned the value that is the result of var1 & var2.

|=

Binary

var1 |= var2;

var1 is assigned the value that is the result of var1 | var2.

∧=

Binary

var1 ∧= var2;

var1 is assigned the value that is the result of var1 ∧ var2.

These work with both Boolean and numeric values in the same way as &, |, and .

NoteNote

Note that the &= and |= assignment operators do not make use of the && and conditional Boolean operators; that is, all operands are processed regardless of the value to the left of the assignment operator.

In the Try It Out that follows, you type in an integer and then the code performs various Boolean evaluations using that integer.

Try it Out: Using Boolean Operators

  1. Create a new console application called Ch04Ex01 and save it in the directory C:\BegVCSharp\Chapter04.

  2. Add the following code to Program.cs:

          static void Main(string[] args)
          {
             Console.WriteLine(″Enter an integer:″);
             int myInt = Convert.ToInt32(Console.ReadLine());
             bool isLessThan10 = myInt < 10;
             bool isBetween0And5 = (0 <= myInt) && (myInt <= 5);
             Console.WriteLine(″Integer less than 10? {0}″, isLessThan10);
             Console.WriteLine(″Integer between 0 and 5? {0}″, isBetween0And5);
             Console.WriteLine(″Exactly one of the above is true? {0}″,
                 isLessThan10 ∧ isBetween0And5);
             Console.ReadKey();
          }
    
  3. Execute the application and enter an integer when prompted. The result is shown in Figure 4-1.

    Figure 4-1

    Referenced Image

TipTip

The first two lines of code prompt for and accept an integer value using techniques you've already seen:

         Console.WriteLine(″Enter an integer:″);
         int myInt = Convert.ToInt32(Console.ReadLine());

You use Convert.ToInt32()to obtain an integer from the string input, which is simply another conversion command in the same family as the Convert.ToDouble()command used previously.

Next, two Boolean variables, isLessThan10 and isBetween0And5, are declared and assigned values with logic that matches the description in their names:

         bool isLessThan10 = myInt < 10;
         bool isBetween0And5 = (0 <= myInt) && (myInt <= 5);

These variables are used in the next three lines of code, the first two of which output their values, while the third performs an operation on them and outputs the result. You work through this code assuming that the user enters 7, as shown in the screenshot.

The first output is the result of the operation myInt < 10. If myInt is 6, which is less than 10, then the result is true, which is what you see displayed. Values of myInt of 10 or higher result in false.

The second output is a more involved calculation: (0 <= myInt) && (myInt <= 5). This involves two comparison operations, to determine whether myInt is greater than or equal to 0 and less than or equal to 5, and a Boolean AND operation on the results obtained. With a value of 6, (0 <= myInt)returns true, and (myInt <= 5)returns false. The result is then (true) && (false), which is false, as you can see from the display.

Finally, you perform a logical exclusive OR on the two Boolean variables isLessThan10 and isBetween0And5. This will return true if one of the values is true and the other false, so only if myInt is 6, 7, 8, or 9. With a value of 6, as in the example, the result is true.

Bitwise Operators

The & and | operators you saw earlier serve an additional purpose: They may be used to perform operations on numeric values. When used in this way, they operate on the series of bits stored in a variable, rather than the value of the variable, which is why they are referred to as bitwise operators.

In this section you will look at these and other bitwise operators that are defined by the C# language. Using this functionality is fairly uncommon in most development, apart from mathematical applications. For that reason there is no Try it Out for this section.

Let's start by considering & and | in turn. Each bit in the first operand is compared with the bit in the same position in the second operand, resulting in the bit in the same position in the resultant value being assigned a value, as shown here:

Operand 1 Bit

Operand 2 Bit

& Result Bit

1

1

1

1

0

0

0

1

0

0

0

0

| is similar, but the result bits are different:

Operand 1 Bit

Operand 2 Bit

& Result BIt

1

1

1

1

0

1

0

1

1

0

0

0

For example, consider the operation shown here:

int result, op1, op2;
op1 = 4;
op2 = 5;
result = op1 & op2;

In this case, you must consider the binary representations of op1 and op2, which are 100 and 101, respectively. The result is obtained by comparing the binary digits in equivalent positions in these two representations as follows:

  • The leftmost bit of result is 1 if the leftmost bits of op1 and op2 are both 1, or 0 otherwise.

  • The next bit of result is 1 if the next bits of op1 and op2 are both 1, or 0 otherwise.

  • Continue for all remaining bits.

In this example, the leftmost bits of op1 and op2 are both 1, so the leftmost bit of result will be 1, too. The next bits are both 0, and the third bits are 1 and 0, respectively, so the second and third bits of result will be 0. The final value of result in binary representation is therefore 100, so the result is assigned the value 4. This is shown graphically in the following equations:

Referenced image.

The same process occurs if you use the | operator, except that in this case each result bit is 1 if either of the operand bits in the same position is 1, as shown in the following equations:

Referenced image

You can also use the operator in the same way, where each result bit is 1 if one or other of the operand bits in the same position is 1, but not both, as shown in the following table:

Operand 1 Bit

Operand 2 Bit

∧ Result Bit

1

1

0

1

0

1

0

1

1

0

0

0

C# also allows the use of a unary bitwise operator (), which acts on its operand by inverting each of its bits, so that the result is a variable having values of 1 for each bit in the operand that is 0, and vice versa. This is shown in the following table:

Operand Bit

~ Result Bit

1

0

0

1

The way integer numbers are stored in .NET, known as two's complement, means that using the unary operator can lead to results that look a little odd. If you remember that an int type is a 32-bit number, for example, then knowing that the operator acts on all 32 of those bits can help you to see what is going on. For example, the number 5 in its full binary representation is as follows:

000000000000000000000000000000101

This is the number −5:

111111111111111111111111111111011

In fact, by the two's complement system, (−x) is defined as (∼x + 1). That may seem odd, but this system is very useful when it comes to adding numbers. For example, adding 10 and −5 (that is, subtracting 5 from 10) looks like this in binary format:

   000000000000000000000000000001010
+  111111111111111111111111111111011
= 1000000000000000000000000000000101
NoteNote

By ignoring the 1 on the far left, you are left with the binary representation for 5, so while results such as ∼1 = −2 may look odd, the underlying structures force this result.

The bitwise operations you've seen in this section are quite useful in certain situations, because they enable an easy method of using individual variable bits to store information. Consider a simple representation of a color using three bits to specify red, green, and blue content. You can set these bits independently to change the three bits to one of the configurations shown in the following table:

Bits

Decimal Representation

Meaning

000

0

black

100

4

red

010

2

green

001

1

blue

101

5

magenta

110

6

yellow

011

3

cyan

111

7

white

Suppose you store these values in a variable of type int. Starting from a black color—that is, an int variable with the value of 0—you can perform operations like this:

int myColor = 0;
bool containsRed;
myColor = myColor | 2;            // Add green bit, myColor now stores 010
myColor = myColor | 4;            // Add red bit, myColor now stores 110
containsRed = (myColor & 4) == 4; // Check value of red bit

The final line of code assigns a value of true to containsRed, as the red bit of myColor is 1. This technique can be quite useful for making efficient use of information, particularly because the operations involved can be used to check the values of multiple bits simultaneously (32 in the case of int values). However, there are better ways to store extra information in single variables (making use of the advanced variable types discussed in the next chapter).

In addition to these four bitwise operators, this section considers two others, shown in the following table:

Operator

Category

Example Expression

Result

>>

Binary

var1 = var2 >> var3;

var1 is assigned the value obtained when the binary content of var2 is shifted var3 bits to the right.

<<

Binary

var1 = var2 << var3;

var1 is assigned the value obtained when the binary content of var2 is shifted var3 bits to the left.

These operators, commonly called bitwise shift operators, are best illustrated with a quick example:

int var1, var2 = 10, var3 = 2;
var1 = var2 << var3;

Here, var1 is assigned the value 40. This can be explained by considering that the binary representation of 10 is 1010, which shifted to the left by two places is 101000—the binary representation of 40. In effect, you have carried out a multiplication operation. Each bit shifted to the left multiplies the value by 2, so two bit-shifts to the left result in multiplication by 4. Conversely, each bit shifted to the right has the effect of dividing the operand by 2, with any non-integer remainder being lost:

int var1, var2 = 10;
var1 = var2 >> 1;

In this example, var1 contains the value 5, whereas the following code results in a value of 2:

int var1, var2 = 10;
var1 = var2 >> 2;

You are unlikely to use these operators in most code, but it is worth being aware of their existence. Their primary use is in highly optimized code, where the overhead of other mathematical operations just won't do. For this reason, they are often used in, for example, device drivers or system code.

The bitwise shift operators also have assignment operators, as shown in the following table:

Operator

Category

Example Expression

Result

>>=

Unary

var1 >>= var2;

var1 is assigned the value obtained when the binary content of var1 is shifted var2 bits to the right.

<<=

Unary

var1 <<= var2;

var1 is assigned the value obtained when the binary content of var1 is shifted var2 bits to the left.

Operator Precedence Updated

Now that you have a few more operators to consider, the operator precedence table from the previous chapter should be updated to include them. The new order is shown in the following table:

Precedence

Operators

Highest

++, −− (used as prefixes); (), +, (unary), !, ˜

*, /, %

+,

<<, >>

<, >, <=, >=

==, !=

&

|

&&

=, *=, /=, %=, +=, −=, <<=, >>=, &=, ∧=, |=

Lowest

++, –– (used as suffixes)

This adds quite a few more levels but explicitly defines how expressions such as the following will be evaluated, where the && operator is processed after the <= and >= operators (in this code var2 is an int value):

var1 = var2 <= 4 && var2 >= 2;

It doesn't hurt to add parentheses to make expressions such as this one clearer. The compiler knows what order to process operators in, but we humans are prone to forget such things (and you might want to change the order). Writing the previous expression as

var1 = (var2 <= 4) && (var2 >= 2);

solves this problem by explicitly ordering the computation.

C# enables you to label lines of code and then jump straight to them using the goto statement. This has its benefits and problems. The main benefit is that it's a simple way to control what code is executed when. The main problem is that excessive use of this technique can result in spaghetti code that is difficult to understand.

The goto statement is used as follows:

goto <labelName>;

Labels are defined as follows:

<labelName>:

For example, consider the following:

int myInteger = 5;
goto myLabel;
myInteger += 10;
myLabel:
Console.WriteLine(″myInteger = {0}″, myInteger);

Execution proceeds as follows:

  • myInteger is declared as an int type and assigned the value 5.

  • The goto statement interrupts normal execution and transfers control to the line marked myLabel:.

  • The value of myInteger is written to the console.

The highlighted line in the following code is never executed:

int myInteger = 5;
goto myLabel;
myInteger += 10;
myLabel:
Console.WriteLine(″myInteger = {0}″, myInteger);

In fact, if you try to compile this code in an application, the Error List window will show a warning labeled ″Unreachable code detected,″ along with location details. You will also see a wavy green line under myInteger on the unreachable line of code.

goto statements have their uses, but they can make things very confusing indeed. In fact, if you can avoid it (and by using the techniques you'll learn in the remainder of this chapter you'll be able to), never use goto. The following example shows some spaghetti code arising from the use of this unfortunate keyword:

start:
int myInteger = 5;
goto addVal;
writeResult:
Console.WriteLine(″myInteger = {0}″, myInteger);
goto start;
addVal:
myInteger += 10;
goto writeResult;

This is perfectly valid code but very difficult to read! Try it out for yourself and see what happens. Before doing that, though, try to first determine what this code will do by looking at it, and then give yourself a pat on the back if you're right. You'll revisit this statement a little later, because it has implications for use with some of the other structures in this chapter.

Branching is the act of controlling which line of code should be executed next. The line to jump to is controlled by some kind of conditional statement. This conditional statement is based on a comparison between a test value and one or more possible values using Boolean logic.

This section describes three branching techniques available in C#:

  • The ternary operator

  • The if statement

  • The switch statement

The Ternary Operator

The simplest way to perform a comparison is to use the ternary (or conditional) operator mentioned in the last chapter. You've already seen unary operators that work on one operand, and binary operators that work on two operands, so it won't come as a surprise that this operator works on three operands. The syntax is as follows:

<test> ? <resultIfTrue>: <resultIfFalse>

Here, <test> is evaluated to obtain a Boolean value, and the result of the operator is either <resultIfTrue> or <resultIfFalse> based on this value.

You might use this as follows to test the value of an int variable called myInteger:

string resultString = (myInteger < 10) ? ″Less than 10″
                                  : ″Greater than or equal to 10″;

The result of the ternary operator is one of two strings, both of which may be assigned to resultString. The choice of which string to assign is made by comparing the value of myInteger to 10, where a value of less than 10 results in the first string being assigned, and a value of greater than or equal to 10 results in the second string being assigned. For example, if myInteger is 4, then resultString will be assigned the string Less than 10.

This operator is fine for simple assignments such as this, but it isn't really suitable for executing larger amounts of code based on a comparison. A much better way to do this is to use the if statement.

The if Statement

The if statement is a far more versatile and useful way to make decisions. Unlike ?: statements, if statements don't have a result (so you can't use them in assignments); instead, you use the statement to conditionally execute other statements.

The simplest use of an if statement is as follows, where <test> is evaluated (it must evaluate to a Boolean value for the code to compile) and the line of code that follows the statement is executed if <test> evaluates to true:

if (<test>) 
   <code executed if <test> is true>;

After this code is executed, or if it isn't executed due to <test> evaluating to false, program execution resumes at the next line of code.

You can also specify additional code using the else statement in combination with an if statement. This statement is executed if <test> evaluates to false:

if (<test>)
<code executed if <test> is true>;
else
<code executed if <test> is false>;

Both sections of code can span multiple lines using blocks in braces:

if (<test>)
{
   <code executed if <test> is true>;
}
else
{
   <code executed if <test> is false>;
}

As a quick example, you could rewrite the code from the last section that used the ternary operator:

string resultString = (myInteger < 10) ? ″Less than 10″
                                  : ″Greater than or equal to 10″;

Because the result of the if statement cannot be assigned to a variable, you have to assign a value to the variable in a separate step:

string resultString;
if (myInteger < 10)
   resultString = ″Less than 10″;
else
   resultString = ″Greater than or equal to 10″;

Code such as this, although more verbose, is far easier to read and understand than the equivalent ternary form, and enables far more flexibility.

The following Try It Out illustrates the use of the if statement.

Try it Out: Using the if Statement

  1. Create a new console application called Ch04Ex02 and save it in the directory C:\BegVCSharp\Chapter04.

  2. Add the following code to Program.cs:

          static void Main(string[] args)
          {
             string comparison;
             Console.WriteLine(″Enter a number:″);
             double var1 = Convert.ToDouble(Console.ReadLine());
             Console.WriteLine(″Enter another number:″);
             double var2 = Convert.ToDouble(Console.ReadLine());
             if (var1 < var2)
                comparison = ″less than″;
             else
             {
                if (var1 == var2)
                   comparison = ″equal to″;
                else
                    comparison = ″greater than″;
             }
             Console.WriteLine(″The first number is {0} the second number.″,
                               comparison);
             Console.ReadKey();
          }
    
  3. Execute the code and enter two numbers at the prompts (see Figure 4-2).

    Figure 4-2

    Referenced Image

    Figure 4-2

How It Works

The first section of code is very familiar. It simply obtains two double values from user input:

         string comparison;
         Console.WriteLine(″Enter a number:″);         
         double var1 = Convert.ToDouble(Console.ReadLine());  
         Console.WriteLine(″Enter another number:″);         
         double var2 = Convert.ToDouble(Console.ReadLine());

Next, you assign a string to the string variable comparison based on the values obtained for var1 and var2. First you check whether var1 is less than var2:

         if (var1 < var2)            comparison = ″less than″;

If this isn't the case, then var1 is either greater than or equal to var2. In the else section of the first comparison, you need to nest a second comparison:

         else         {            if (var1 == var2)               comparison = ″equal to″;

The else section of this second comparison is reached only if var1 is greater than var2:

            else                comparison = ″greater than″;         }

Finally, you write the value of comparison to the console:

         Console.WriteLine(″The first number is {0} the second number.″,                           comparison);

The nesting used here is just one method of performing these comparisons. You could equally have written this:

         if (var1 < var2)            comparison = ″less than″;         if (var1 == var2)            comparison = ″equal to″;         if (var1 > var2)             comparison = ″greater than″;

The disadvantage with this method is that you are performing three comparisons regardless of the values of var1 and var2. With the first method, you perform only one comparison if var1 < var2 is true, and two comparisons otherwise (you also perform the var1 == var2 comparison), resulting in fewer lines of code being executed. The difference in performance here is slight, but it would be significant in applications where speed of execution is crucial.

Checking More Conditions Using if Statements

In the preceding example, you checked for three conditions involving the value of var1. This covered all possible values for this variable. Sometimes, you might want to check for specific values—for example, if var1 is equal to 1, 2, 3, or 4, and so on. Using code such as the preceding can result in annoyingly nested code:

if (var1 == 1)
{
   // Do something.
}
else
{
   if (var1 == 2)
   {
      // Do something else.
   }
   else
   {
      if (var1 == 3 ‖ var1 == 4)
      {
         // Do something else.
      }
      else
      {
         // Do something else.
      }
   }
}
Caution noteCaution

It's a common mistake to write conditions such as if (var1 == 3 ‖ var1 == 4) as if (var1 == 3 ‖ 4). Here, owing to operator precedence, the == operator is processed first, leaving the operator to operate on a Boolean and a numeric operand, which causes an error.

In these situations, consider using a slightly different indentation scheme and contracting the section of code for the else blocks (that is, using a single line of code after the else blocks, rather than a block of code). That way, you end up with a structure involving else if statements:

if (var1 == 1)
{
   // Do something.
}
else if (var1 == 2)
{
   // Do something else.
}
else if (var1 == 3 ‖ var1 == 4)
{
   // Do something else.
}
else
{
   // Do something else.
}

These else if statements are really two separate statements, and the code is functionally identical to the previous code, but much easier to read. When making multiple comparisons such as this, consider using the switch statement as an alternative branching structure.

The switch Statement

The switch statement is similar to the if statement in that it executes code conditionally based on the value of a test. However, switch enables you to test for multiple values of a test variable in one go, rather than just a single condition. This test is limited to discrete values, rather than clauses such as ″greater than X,″ so its use is slightly different; but it can be a powerful technique.

The basic structure of a switch statement is as follows:

switch (<testVar>)
{
   case <comparisonVal1>:
      <code to execute if <testVar> == <comparisonVal1> >
      break;
   case <comparisonVal2>:
      <code to execute if <testVar> == <comparisonVal2> >
      break;
   …
   case <comparisonValN>:
      <code to execute if <testVar> == <comparisonValN> >
      break;
   default:
      <code to execute if <testVar> != comparisonVals>
      break;
}

The value in <testVar> is compared to each of the <comparisonValX> values (specified with case statements). If there is a match, then the code supplied for this match is executed. If there is no match, then the code in the default section is executed if this block exists.

On completion of the code in each section, you have an additional command, break. It is illegal for the flow of execution to reach a second case statement after processing one case block.

NoteNote

The behavior where the flow of execution is forbidden from flowing from one case block to the next is one area in which C# differs from C++. In C++ the processing of case statements is allowed to run from one to another.

The break statement here simply terminates the switch statement, and processing continues on the statement following the structure.

There are alternative methods of preventing flow from one case statement to the next in C# code. You can use the return statement, which results in termination of the current function, rather than just the switch structure (see Chapter 6 for more details about this), or a goto statement. goto statements (as detailed earlier) work here because case statements actually define labels in C# code. Here is an example:

switch (<testVar>)
{
   case <comparisonVal1>:
      <code to execute if <testVar> == <comparisonVal1> >
      goto case <comparisonVal2>;
   case <comparisonVal2>:
      <code to execute if <testVar> == <comparisonVal2> >
      break;
   …

One exception to the rule that the processing of one case statement can't run freely into the next: If you place multiple case statements together (stack them) before a single block of code, then you are in effect checking for multiple conditions at once. If any of these conditions is met, then the code is executed. Here's an example:

switch (<testVar>)
{
   case <comparisonVal1>:
   case <comparisonVal2>:
      <code to execute if <testVar> == <comparisonVal1> or
                          <testVar> == <comparisonVal2> >
      break;
   …

These conditions also apply to the default statement. There is no rule stipulating that this statement must be the last in the list of comparisons, and you can stack it with case statements if you want. Adding a breakpoint with break, goto, or return ensures that a valid execution path exists through the structure in all cases.

Each of the <comparisonValX> comparisons must be a constant value. One way of doing this is to provide literal values, like this:

switch (myInteger)
{
   case 1:
      <code to execute if myInteger == 1>
      break;
   case −1:
      <code to execute if myInteger == −1>
      break;
   default:
      <code to execute if myInteger != comparisons>
      break;
}

Another way is to use constant variables. Constant variables (also known as just ″constants,″ avoiding the oxymoron) are just like any other variable except for one key factor: The value they contain never changes. Once you assign a value to a constant variable, then that is the value it has for the duration of code execution. Constant variables can come in handy here, because it is often easier to read code where the actual values being compared are hidden from you at the time of comparison.

You declare constant variables using the const keyword in addition to the variable type, and you must assign them values at this time, as shown here:

const int intTwo = 2;

The preceding code is perfectly valid, but if you try

const int intTwo;
intTwo = 2;

you will get an error and won't be able to compile your code. This also happens if you try to change the value of a constant variable through any other means after initial assignment.

Try it Out: Using the switch Statement

The following Try It Out uses a switch statement to write different strings to the console, depending on the value you enter for a test string.

  1. Create a new console application called Ch04Ex03 and save it to the directory C:\BegVCSharp\Chapter04.

  2. Add the following code to Program.cs:

          static void Main(string[] args)
          {
             const string myName = ″karli″;
             const string sexyName = ″angelina″;
             const string sillyName = ″ploppy″;
             string name;
             Console.WriteLine(″What is your name?″);
             name = Console.ReadLine();
             switch (name.ToLower())
             {
                case myName:
                   Console.WriteLine(″You have the same name as me!″);
                   break;
                case sexyName:
                   Console.WriteLine(″My, what a sexy name you have!″);
                   break;
                case sillyName:
                   Console.WriteLine(″That's a very silly name.″);
                   break;
             }
             Console.WriteLine(″Hello {0}!″, name);
             Console.ReadKey();
          }
    
  3. Execute the code and enter a name. The result is shown in Figure 4-3.

    Figure 4-3

    Referenced Image

    Figure 4-3

TipTip

The code sets up three constant strings, accepts a string from the user, and then writes out text to the console based on the string entered. Here, the strings are names.

When you compare the name entered (in the variable name) to your constant values, you first force it into lowercase with name.ToLower(). This is a standard command that works with all string variables, and it comes in handy when you're not sure what the user entered. Using this technique, the strings Karli, kArLi, karli, and so on all match the test string karli.

The switch statement itself attempts to match the string entered with the constant values you have defined, and, if successful, writes out a personalized message to greet the user. If no match is made, you offer a generic greeting.

switch statements place no limit on the amount of case sections they contain, so you could extend this code to cover every name you can think of should you want … but it might take a while!

Looping refers to the repeated execution of statements. This technique comes in very handy because it means that you can repeat operations as many times as you want (thousands, even millions, of times) without having to write the same code each time.

As a simple example, consider the following code for calculating the amount of money in a bank account after 10 years, assuming that interest is paid each year and no other money flows into or out of the account:

double balance = 1000;
double interestRate = 1.05; // 5% interest/year
balance *= interestRate;
balance *= interestRate;
balance *= interestRate;
balance *= interestRate;
balance *= interestRate;
balance *= interestRate;
balance *= interestRate;
balance *= interestRate;
balance *= interestRate;
balance *= interestRate;

Writing the same code 10 times seems a bit wasteful, and what if you wanted to change the duration from 10 years to some other value? You'd have to manually copy the line of code the required amount of times, which would be a bit of a pain! Luckily, you don't have to do this. Instead, you can have a loop that executes the instruction you want the required number of times.

Another important type of loop is one in which you loop until a certain condition is fulfilled. These loops are slightly simpler than the situation detailed previously (although no less useful), so they're a good starting point.

do Loops

do loops operate as follows. The code you have marked out for looping is executed, a Boolean test is performed, and the code executes again if this test evaluates to true, and so on. When the test evaluates to false, the loop exits.

The structure of a do loop is as follows, where <Test> evaluates to a Boolean value:

do
{
   <code to be looped>
} while (<Test>);
NoteNote

The semicolon after the while statement is required.

For example, you could use the following to write the numbers from 1 to 10 in a column:

int i = 1;
do
{
   Console.WriteLine(″{0}″, i++);
} while (i <= 10);

Here, you use the suffix version of the ++ operator to increment the value of i after it is written to the screen, so you need to check for i <= 10 to include 10 in the numbers written to the console.

The following Try It Out uses this for a slightly modified version of the code shown earlier, where you calculated the balance in an account after 10 years. Here, you use a loop to calculate how many years it will take to get a specified amount of money in the account, based on a starting amount and a fixed interest rate.

Try it Out: Using do Loops

  1. Create a new console application called Ch04Ex04 and save it to the directory C:\BegVCSharp\Chapter04.

  2. Add the following code to Program.cs:

      static void Main(string[] args)
       {
          double balance, interestRate, targetBalance;
          Console.WriteLine(″What is your current balance?″);
          balance = Convert.ToDouble(Console.ReadLine());
          Console.WriteLine(″What is your current annual interest rate (in %)?″);
          interestRate = 1 + Convert.ToDouble(Console.ReadLine()) / 100.0;
          Console.WriteLine(″What balance would you like to have?″);
          targetBalance = Convert.ToDouble(Console.ReadLine());
          int totalYears = 0;
          do
          {
             balance *= interestRate;
             ++totalYears;
          }
          while (balance < targetBalance);
          Console.WriteLine(″In {0} year{1} you'll have a balance of {2}.″,
                            totalYears, totalYears == 1 ? ″″: ″s″, balance);
          Console.ReadKey();
       }
    
  3. Execute the code and enter some values. A sample result is shown in Figure 4-4.

    Figure 4-4

    Referenced Image

    Figure 4-4

How It Works

This code simply repeats the simple annual calculation of the balance with a fixed interest rate as many times as is necessary for the balance to satisfy the terminating condition. You keep a count of how many years have been accounted for by incrementing a counter variable with each loop cycle:

        int totalYears = 0;
         do
         {
            balance *= interestRate;
            ++totalYears;
         }
         while (balance < targetBalance);

You can then use this counter variable as part of the result output:

         Console.WriteLine(″In {0} year{1} you'll have a balance of {2}.″,
                           totalYears, totalYears == 1 ? ″″: ″s″, balance);

Perhaps the most common usage of the ?: (ternary) operator is to conditionally format text with the minimum of code. Here, you output an ″s″ after ″year″ if totalYears isn't equal to 1.

Unfortunately, this code isn't perfect. Consider what happens when the target balance is less than the current balance. The output will be similar to what is shown in Figure 4-5.

Figure 4-5

Referenced Image

Figure 4-5

do loops always execute at least once. Sometimes, as in this situation, this isn't ideal. Of course, you could add an if statement:

         int totalYears = 0;
         if (balance < targetBalance)
         {
            do
            {
               balance *= interestRate;
               ++totalYears;
            }
            while (balance < targetBalance);
         }
         Console.WriteLine(″In {0} year{1} you'll have a balance of {2}.″,
                           totalYears, totalYears == 1 ? ″″: ″s″, balance);

Clearly, this adds unnecessary complexity. A far better solution is to use a while loop.

while Loops

while loops are very similar to do loops, but they have one important difference: The Boolean test in a while loop takes place at the start of the loop cycle, not at the end. If the test evaluates to false, then the loop cycle is never executed. Instead, program execution jumps straight to the code following the loop.

Here's how while loops are specified:

while (<Test>)
{
   <code to be looped>
}

They can be used in almost the same way as do loops:

int i = 1;
while (i <= 10)
{
   Console.WriteLine(″{0}″, i++);
}

This code has the same result as the do loop shown earlier; it outputs the numbers 1 to 10 in a column. The following Try It Out demonstrates how you can modify the last example to use a while loop.

Try it Out: Using while Loops

  1. Create a new console application called Ch04Ex05 and save it to the directory C:\BegVCSharp\Chapter04.

  2. Modify the code as follows (use the code from Ch04Ex04 as a starting point, and remember to delete the while statement at the end of the original do loop):

       static void Main(string[] args)
       {
          double balance, interestRate, targetBalance;
          Console.WriteLine(″What is your current balance?″);
          balance = Convert.ToDouble(Console.ReadLine());
          Console.WriteLine(″What is your current annual interest rate (in %)?″);
          interestRate = 1 + Convert.ToDouble(Console.ReadLine()) / 100.0;
          Console.WriteLine(″What balance would you like to have?″);
          targetBalance = Convert.ToDouble(Console.ReadLine());
          int totalYears = 0;
          while (balance < targetBalance)
          {
             balance *= interestRate;
             ++totalYears;
          }
          Console.WriteLine(″In {0} year{1} you'll have a balance of {2}.″,
                            totalYears, totalYears == 1 ? ″″: ″s″, balance);
             if (totalYears == 0)
                Console.WriteLine(
                   ″To be honest, you really didn't need to use this calculator.″);
          Console.ReadKey();
       }
    
  3. Execute the code again, but this time use a target balance that is less than the starting balance, as shown in Figure 4-6.

    Figure 4-6

    Referenced Image

    Figure 4-6

TipTip

This simple change from a do loop to a while loop has solved the problem in the last example. By moving the Boolean test to the beginning, you provide for the circumstance where no looping is required, and you can jump straight to the result.

Of course, other alternatives are possible in this situation. For example, you could check the user input to ensure that the target balance is greater than the starting balance. In that case, you can place the user input section in a loop as follows:

         Console.WriteLine(″What balance would you like to have?″);
         do
         {
            targetBalance = Convert.ToDouble(Console.ReadLine());
            if (targetBalance <= balance)
               Console.WriteLine(″You must enter an amount greater than ″ +
                         ″your current balance!\nPlease enter another value.″);
         }
         while (targetBalance <= balance);

This rejects values that don't make sense, so the output looks like Figure 4-7.

Figure 4-7

Referenced Image

Figure 4-7

This validation of user input is an important topic when it comes to application design, and many examples of it appear throughout this book.

for Loops

The last type of loop to look at in this chapter is the for loop. This type of loop executes a set number of times and maintains its own counter. To define a for loop you need the following information:

  • A starting value to initialize the counter variable

  • A condition for continuing the loop, involving the counter variable

  • An operation to perform on the counter variable at the end of each loop cycle

For example, if you want a loop with a counter that increments from 1 to 10 in steps of one, then the starting value is 1; the condition is that the counter is less than or equal to 10; and the operation to perform at the end of each cycle is to add 1 to the counter.

This information must be placed into the structure of a for loop as follows:

for (<initialization>; <condition>; <operation>)
{
   <code to loop>
}

This works exactly the same way as the following while loop:

<initialization>
while (<condition>)
{
   <code to loop>
   <operation>
}

The format of the for loop makes the code easier to read, however, because the syntax involves the complete specification of the loop in one place, rather than dividing it over several statements in different areas of the code.

Earlier, you used do and while loops to write out the numbers from 1 to 10. The code that follows shows what is required to do this using a for loop:

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

The counter variable, an integer called i, starts with a value of 1 and is incremented by 1 at the end of each cycle. During each cycle, the value of i is written to the console.

When the code resumes after the loop, i has a value of 11. That's because at the end of the cycle where i is equal to 10, i is incremented to 11. This happens before the condition i <= 10 is processed, at which point the loop ends. As with while loops, for loops execute only if the condition evaluates to true before the first cycle, so the code in the loop doesn't necessarily run at all.

As a final note, you can declare the counter variable as part of the for statement, rewriting the preceding code as follows:

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

If you do this, though, the variable i won't be accessible from code outside this loop (see the section ″Variable Scope″ in Chapter 6).

The next Try It Out uses for loops, and because you have already used several loops now, this example is a bit more interesting: It displays a Mandelbrot set (but using plain-text characters, so it won't look that spectacular).

Try it Out: Using for Loops

  1. Create a new console application called Ch04Ex06 and save it to the directory C:\BegVCSharp\Chapter04.

  2. Add the following code to Program.cs:

          static void Main(string[] args)
          {
             double realCoord, imagCoord;
             double realTemp, imagTemp, realTemp2, arg;
             int iterations;
             for (imagCoord = 1.2; imagCoord >= -1.2; imagCoord -= 0.05)
             {
                for (realCoord = -0.6; realCoord <= 1.77; realCoord += 0.03)
                {
                   iterations = 0;
                   realTemp = realCoord;
                   imagTemp = imagCoord;
                   arg = (realCoord * realCoord) + (imagCoord * imagCoord);
                   while ((arg < 4) && (iterations < 40))
                   {
                      realTemp2 = (realTemp * realTemp)-(imagTemp * imagTemp)
                            -realCoord;
                      imagTemp = (2 * realTemp * imagTemp) -imagCoord;
                      realTemp = realTemp2;
                      arg = (realTemp * realTemp) + (imagTemp * imagTemp);
                      iterations += 1;
                   }
                   switch (iterations % 4)
                   {
                      case 0:
                         Console.Write(″.″);
                         break;
                      case 1:
                         Console.Write(″o″);
                         break;
                      case 2:
                         Console.Write(″O″);
                         break;
                      case 3:
                         Console.Write(″@″);
                         break;
                   }
                }
                Console.Write(″\n″);
             }
             Console.ReadKey();
          }
    
  3. Execute the code. The result is shown in Figure 4-8.

    Figure 4-8

    Referenced Image

    Figure 4-8

TipTip

Details about calculating Mandelbrot sets are beyond the scope of this chapter, but you should understand why you need the loops used in this code. Feel free to skim through the following two paragraphs if the mathematics doesn't interest you; it's an understanding of the code that is important here.

Each position in a Mandelbrot image corresponds to an imaginary number of the form N = x + y*i, where the real part is x, the imaginary part is y, and i is the square root of -1. The x and y coordinates of the position in the image correspond to the x and y parts of the imaginary number.

For each position on the image, you look at the argument of N, which is the square root of x*x + y*y. If this value is greater than or equal to 2, you say that the position corresponding to this number has a value of 0. If the argument of N is less than 2, you change N to a value of N*N-N (giving you N = (x*x-y*y-x) + (2*x*y-y)*i) and check the argument of this new value of N again. If this value is greater than or equal to 2, you say that the position corresponding to this number has a value of 1. This process continues until you either assign a value to the position on the image or perform more than a certain number of iterations.

Based on the values assigned to each point in the image, you would, in a graphical environment, place a pixel of a certain color on the screen. However, because you are using a text display, you simply place characters onscreen instead.

Now, back to the code, and the loops contained in it. You begin by declaring the variables you need for your calculation:

         double realCoord, imagCoord;
         double realTemp, imagTemp, realTemp2, arg;
         int iterations;

Here, realCoord and imagCoord are the real and imaginary parts of N, and the other double variables are for temporary information during computation. iterations records how many iterations it takes before the argument of N (arg) is 2 or greater.

Next, you start two for loops to cycle through coordinates covering the whole of the image (using a slightly more complex syntax for modifying your counters than ++ or --, a common and powerful technique):

         for (imagCoord = 1.2; imagCoord >= -1.2; imagCoord -= 0.05)
         {
            for (realCoord = -0.6; realCoord <= 1.77; realCoord += 0.03)
            {

Here, appropriate limits have been used to show the main section of the Mandelbrot set. Feel free to play around with these if you want to try ″zooming in″ on the image.

Within these two loops you have code that pertains to a single point in the Mandelbrot set, giving you a value for N to play with. This is where you perform your calculation of iterations required, giving you a value to plot for the current point.

First, initialize some variables:

               iterations = 0;
               realTemp = realCoord;
               imagTemp = imagCoord;
               arg = (realCoord * realCoord) + (imagCoord * imagCoord);

Next, you have a while loop to perform your iterating. Use a while loop rather than a do loop, in case the initial value of N has an argument greater than 2 already, in which case iterations == 0 is the answer you are looking for and no further calculations are necessary.

Note that you're not quite calculating the argument fully here. You're just getting the value of x*x + y*y and checking whether that value is less than 4. This simplifies the calculation, because you know that 2 is the square root of 4 and don't have to calculate any square roots yourself:

               while ((arg < 4) && (iterations < 40))
               {
                  realTemp2 = (realTemp * realTemp)-(imagTemp * imagTemp)
                        -realCoord;
                  imagTemp = (2 * realTemp * imagTemp)-imagCoord;
                  realTemp = realTemp2;
                  arg = (realTemp * realTemp) + (imagTemp * imagTemp);
                  iterations += 1;
               }

The maximum number of iterations of this loop, which calculates values as detailed above, is 40.

Once you have a value for the current point stored in iterations, you use a switch statement to choose a character to output. You just use four different characters here, instead of the 40 possible values, and use the modulus operator (%) so that values of 0, 4, 8, and so on provide one character; values of 1, 5, 9, and so on provide another character, and so forth:

               switch (iterations % 4)
               {
                  case 0:
                     Console.Write(″.″);
                     break;
                  case 1:
                     Console.Write(″o″);
                     break;
                  case 2:
                     Console.Write(″O″);
                     break;
                  case 3:
                     Console.Write(″@″);
                     break;
               }

You use Console.Write()here, rather than Console.WriteLine(), because you don't want to start a new line every time you output a character. At the end of one of the innermost for loops, you do want to end a line, so you simply output an end-of-line character using the escape sequence shown earlier:

            }
            Console.Write(″\n″);
         }

This results in each row being separated from the next and lining up appropriately. The final result of this application, though not spectacular, is fairly impressive, and certainly shows how useful looping and branching can be.

Interrupting Loops

Sometimes you want finer-grained control over the processing of looping code. C# provides four commands to help you here, three of which were shown in other situations:

  • break—Causes the loop to end immediately

  • continue—Causes the current loop cycle to end immediately (execution continues with the next loop cycle)

  • goto—Allows jumping out of a loop to a labeled position (not recommended if you want your code to be easy to read and understand)

  • return—Jumps out of the loop and its containing function (see Chapter 6)

The break command simply exits the loop, and execution continues at the first line of code after the loop, as shown in the following example:

int i = 1;
while (i <= 10)
{
   if (i == 6)
      break;
   Console.WriteLine(″{0}″, i++);
}

This code writes out the numbers from 1 to 5 because the break command causes the loop to exit when i reaches 6.

continue only stops the current cycle, not the whole loop, as shown here:

int i;
for (i = 1; i <= 10; i++)
{
   if ((i % 2) == 0)
      continue;
   Console.WriteLine(i);
}

In the preceding example, whenever the remainder of i divided by 2 is zero, the continue statement stops the execution of the current cycle, so only the numbers 1, 3, 5, 7, and 9 are displayed.

The third method of interrupting a loop is to use goto, as shown earlier:

int i = 1;
while (i <= 10)
{
   if (i == 6)
      goto exitPoint;
   Console.WriteLine(″{0}″, i++);
}
Console.WriteLine(″This code will never be reached.″);
exitPoint:
Console.WriteLine(″This code is run when the loop is exited using goto.″);

Note that exiting a loop with goto is legal (if slightly messy), but it is illegal to use goto to jump into a loop from outside.

Infinite Loops

It is possible, through both coding errors and design, to define loops that never end, so-called infinite loops. As a very simple example, consider the following:

while (true)
{
   // code in loop
}

This can be useful, and you can always exit such loops using code such as break statements or manually by using the Windows Task Manager. However, when this occurs by accident, it can be annoying. Consider the following loop, which is similar to the for loop in the previous section:

int i = 1;
while (i <= 10)
{
   if ((i % 2) == 0)
      continue;
   Console.WriteLine(″{0}″, i++);
}

Here, i isn't incremented until the last line of code in the loop, which occurs after the continue statement. If this continue statement is reached (which it will be when i is 2), the next loop cycle will be using the same value of i, continuing the loop, testing the same value of i, continuing the loop, and so on. This will cause the application to freeze. Note that it's still possible to quit the frozen application in the normal way, so you won't have to reboot if this happens.

In this chapter, you increased your programming knowledge by considering various structures that you can use in your code. The proper use of these structures is essential when you start making more complex applications, and you will see them used throughout this book.

You first spent some time looking at Boolean logic, with a bit of bitwise logic thrown in for good measure. Looking back on this after working through the rest of the chapter should confirm the suggestion that this topic is very important when it comes to implementing branching and looping code in your programs. It is essential to become very familiar with the operators and techniques detailed in this section.

Branching enables you to conditionally execute code, which, when combined with looping, enables you to create convoluted structures in your C# code. When you have loops inside loops inside if structures inside loops, you start to see why code indentation is so useful! If you shift all your code to the left of the screen, it instantly becomes difficult to parse by eye, and even more difficult to debug. Make sure you've got the hang of indentation at this stage—you'll appreciate it later! VS does a lot of this for you, but it's a good idea to indent code as you type it anyway.

The next chapter covers variables in more depth.

Exercises

  1. If you have two integers stored in variables var1 and var2, what Boolean test can you perform to determine whether one or the other (but not both) is greater than 10?

  2. Write an application that includes the logic from Exercise 1, obtains two numbers from the user, and displays them, but rejects any input where both numbers are greater than 10 and asks for two new numbers.

  3. What is wrong with the following code?

    int i;
    for (i = 1; i <= 10; i++)
    {
       if ((i % 2) = 0)
          continue;
       Console.WriteLine(i);
    }
    
  4. Modify the Mandelbrot set application to request image limits from the user and display the chosen section of the image. The current code outputs as many characters as will fit on a single line of a console application; consider making every image chosen fit in the same amount of space to maximize the viewable area.

Answers to Exercises can be found in Appendix A.

Topic

Key Concepts

Boolean logic involves using Boolean (true or false) values to evaluate conditions. Boolean operators are used to perform comparisons between values and return Boolean results. Some Boolean operators are also used to perform bitwise operations on the underlying bit structure of values, and there are some specialized bitwise operators too.

You can use Boolean logic to control program flow. The result of an expression that evaluates to a Boolean value can be used to determine whether a block of code is executed. You do this with if statements or the ?: (ternary) operator for simple branching, or the switch statement to check multiple conditions simultaneously.

Looping allows you to execute blocks of code a number of times according to conditions you specify. You can use do and while loops to execute code while a Boolean expression evaluates to true, and for loops to include a counter in your looping code. Loops can be interrupted by cycle (with continue) or completely (with break). Some loops only end if you interrupt them; these are called infinite loops.

Beginning Visual C# 2010, Copyright © 2010 by Wiley Publishing, Inc., ISBN: 978-0-470-50226-6, Published by Wiley Publishing, Inc., All Rights Reserved. Wrox, the Wrox logo, Wrox Programmer to Programmer, and related trade dress are trademarks or registered trademarks of John Wiley & Sons, Inc and/or its affiliates.

Show:
© 2015 Microsoft