Chapter 9. Statements

What Are Statements?

The statements in C# are very similar to those of C and C++. This chapter covers the characteristics of a C# statement, as well as the flow-of-control statements provided by the language.

  • A statement is a source code instruction describing a type or telling the program to perform an action.

  • There are three major categories of statements, as follows:

    • Declaration statements: Statements that declare types or variables

    • Embedded statements: Statements that perform actions or manage flow of control

    • Labeled statements: Statements to which control can jump

Previous chapters have covered a number of different declaration statements, including declarations of local variables, classes, and class members. This chapter will cover the embedded statements, which do not declare types, variables, or instances. Instead, they use expressions and flow-of-control constructs to work with the objects and variables that have been declared by the declaration statements.

  • A simple statement consists of an expression followed by a semicolon.

  • A block is a sequence of statements enclosed by matching curly braces. The enclosed statements can include the following:

    • Declaration statements

    • Embedded statements

    • Labeled statements

    • Nested blocks

The following code gives examples of each:

int x = 10;              // Simple declaration
   int z;                   // Simple declaration


   {                        // Block
      int y = 20;           // Simple declaration
      z = x + y;            // Embedded statement
   top: y = 30;             // Labeled statement
         ...
      {                     // Nested block
         ...
      }
   }

Note

A block counts syntactically as a single embedded statement. Anywhere that an embedded statement is required syntactically, you can use a block.

An empty statement consists of just a semicolon. You can use an empty statement at any position where the syntax of the language requires an embedded statement, but your program logic does not require any action.

For example, the following code is an example of using the empty statement:

  • The second line in the code is an empty statement. It is required because there must be an embedded statement between the if part and the else part of the construct.

  • The fourth line is a simple statement, as shown by the terminating semicolon.

if( x < y )
   ;                     // Empty statement
else
   z = a + b;            // Simple statement

Expression Statements

The last chapter looked at expressions. Expressions return values, but they can also have side effects.

  • A side effect is an action that affects the state of the program.

  • Many expressions are evaluated only for their side effects.

You can create a statement from an expression by placing a statement terminator (semicolon) after it. Any value returned by the expression is discarded. For example, the following code shows an expression statement. It consists of the assignment expression (an assignment operator and two operands) followed by a semicolon. This does the following two things:

  • The expression assigns the value on the right of the operator to the memory location referenced by variable x. Although this is probably the main reason for the statement, this is considered the side effect.

  • After setting the value of x, the expression returns with the new value of x. But there is nothing to receive this return value, so it is ignored.

x = 10;

The whole reason for evaluating the expression is to achieve the side effect.

Flow-of-Control Statements

C# provides the flow-of-control constructs common to modern programming languages.

  • Conditional execution executes or skips a section of code depending on a condition. The conditional execution statements are the following:

    • if

    • if...else

    • switch

  • Looping statements repeatedly execute a section of code. The looping statements are the following:

    • while

    • do

    • for

    • foreach

  • Jump statements change the flow of control from one section of code to a specific statement in another section of code. The jump statements are the following:

    • break

    • continue

    • return

    • goto

    • throw

Conditional execution and looping constructs (other than foreach) require a test expression, or condition, to determine where the program should continue execution.

Note

Unlike C and C++, test expressions must return a value of type bool. Numbers do not have a Boolean interpretation in C#.

The if Statement

The if statement implements conditional execution. The syntax for the if statement is shown here, and is illustrated in Figure 9-1.

  • TestExpr must evaluate to a value of type bool.

  • If TestExpr evaluates to true, Statement is executed.

  • If it evaluates to false, Statement is skipped.

if( TestExpr )

   Statement
The if statement

Figure 9-1. The if statement

The following code shows examples of if statements:

// With a simple statement
   if( x <= 10 )
       z = x - 1;                // Single statement, no curly braces needed



   // With a block
   if( x >= 20 )
   {
       x = x - 5;               // Block--braces needed
       y = x + z;
   }



   int x = 5;
   if( x )             // Error: test expression must be a bool, not int
   {
       ...
   }

The if . . . else Statement

The if...else statement implements a two-way branch. The syntax for the if...else statement is shown here, and is illustrated in Figure 9-2.

  • If TestExpr evaluates to true, Statement1 is executed.

  • If it evaluates to false, Statement2 is executed instead.

If( TestExpr )

   Statement1
else

   Statement2
The if . . . else statement

Figure 9-2. The if . . . else statement

The following is an example of the if...else statement:

If( x <= 10 )
      z = x - 1;               // Single statement
   else
   {                           // Multiple statements--block
      x = x - 5;
      y = x + z;
   }

The switch Statement

The switch statement implements multi-way branching. The syntax and structure of the switch statement are shown in Figure 9-3.

  • The switch statement contains zero or more switch sections.

  • Each switch section starts with one or more switch labels.

Structure of a switch statement

Figure 9-3. Structure of a switch statement

Switch labels have the following form:

Structure of a switch statement

The flow of control through the structure in Figure 9-3 is the following:

  • The test expression, TestExpr, is evaluated at the top of the construct.

  • If the value of TestExpr is equal to the value ConstExpr1, the constant expression in the first switch label, then the statements in the statement list following the switch label are executed, until the break statement is encountered.

  • Each switch section must end with a break statement (or a goto statement, as discussed later).

  • The break statement branches execution to the end of the switch statement.

  • The default section is optional, but must include a break statement, if included.

The general flow of control through a switch statement is illustrated in Figure 9-4. You can modify the flow through a switch statement with a goto statement or a return statement.

The flow of control through a switch statement

Figure 9-4. The flow of control through a switch statement

Note

Unlike C and C++, each switch section, including the optional default section, must end with a break or return statement. In C#, there is no falling through from one switch section to the next.

A Switch Example

The following code executes the switch statement five times, with the value of x ranging from 1 to 5. From the output, you can tell which case section was executed on each cycle through the loop.

for( int x=1; x<6; x++ )
   {
      switch( x )                          // Evaluate the value of variable x.
      {
         case 2:                                        // If x equals 2
            Console.WriteLine
               ("x is {0} -- In Case 2", x);
            break;                                      // Go to end of switch.


         case 5:                                        // If x equals 5
            Console.WriteLine
               ("x is {0} -- In Case 5", x);
            break;                                      // Go to end of switch.


         default:
                                           // If x is neither 2 nor 5
            Console.WriteLine
               ("x is {0} -- In Default case", x);
            break;
                                           // Go to end of switch.
      }
   }

This code produces the following output:

x is 1 -- In Default case
x is 2 -- In Case 2
x is 3 -- In Default case
x is 4 -- In Default case
x is 5 -- In Case 5

More on the switch Statement

A switch statement can have any number of switch sections, including none (although with none, you'll get a compiler warning). The default section is not required, as shown in the following example. It is, however, generally considered good practice to include it, since it can catch potential errors.

For example, the switch statement in the following code has no default section. The switch statement is inside a for loop, which executes the statement five times, with the value of x starting at 1 and ending at 5.

for( int x=1; x<6; x++ )
   {
       switch( x )
       {
          case 5:
             Console.WriteLine("x is {0} -- In Case 5", x);
             break;
       }
   }

This code produces the following output:

x is 5 -- In Case 5

The following code has only the default section:

for( int x=1; x<4; x++ )
   {
      switch( x )
      {
         default:
            Console.WriteLine("x is {0} -- In Default case", x);
            break;
      }
   }

This code produces the following output:

x is 1 -- In Default case
x is 2 -- In Default case
x is 3 -- In Default case

Switch Labels

The expression following the keyword case in a switch label

  • Must be a constant expression, and must therefore be completely evaluable by the compiler at compile time

  • Must be of the same type as the test expression

For example, Figure 9-5 shows three sample switch statements.

Switch statements with different types of switch labels

Figure 9-5. Switch statements with different types of switch labels

Although C# does not allow falling through from one switch section to another

  • You can attach multiple switch labels to any switch section.

  • Following the statement list associated with a case, there must be a break or goto statement before the next switch label, unless there are no intervening executable statements between the switch labels.

For example, in the following code, since there are no executable statements between the first three switch labels, it's fine to have one follow the other. Cases 5 and 6, however, have an executable statement between them, so there must be a break or goto statement before case 6.

switch( x )
   {
      case 1:                    // Acceptable
      case 2:
      case 3:
         ...                     // Execute this code if x equals 1, 2, or 3.
         break;
      case 5:
         y = x + 1;
      case 6:                    // Not acceptable because there is no break
         ...

The while Loop

The while loop is a simple loop construct in which the test expression is performed at the top of the loop. The syntax of the while loop is shown here, and is illustrated in Figure 9-6.

  • First, TestExpr is evaluated.

  • If TestExpr evaluates to false, then execution continues after the end of the while loop.

  • Otherwise, when TestExpr evaluates to true, then Statement is executed, and TestExpr is evaluated again. Each time TestExpr evaluates to true, Statement is executed another time. The loop ends when TestExpr evaluates to false.

while( TestExpr )

   Statement
The while loop

Figure 9-6. The while loop

The following code shows an example of the while loop, where the test expression variable starts with a value of 3 and is decremented at each iteration. The loop exits when the value of the variable becomes 0.

int x = 3;
   while( x > 0 )
   {
      Console.WriteLine("x:  {0}", x);
      x--;
   }
   Console.WriteLine("Out of loop");

This code produces the following output:

x:  3
x:  2
x:  1
Out of loop

The do Loop

The do loop is a simple loop construct in which the test expression is performed at the bottom of the loop. The syntax for the do loop is shown here and illustrated in Figure 9-7.

  • First, Statement is executed.

  • Then, TestExpr is evaluated.

  • If TestExpr returns true, then Statement is executed again.

  • Each time TestExpr returns true, Statement is executed again.

  • When TestExpr returns false, control passes to the statement following the end of the loop construct.

do
   Statement
while( TestExpr );             // End of do loop
The do loop

Figure 9-7. The do loop

The do loop has several characteristics that set it apart from other flow-of-control constructs. They are the following:

  • The body of the loop, Statement, will always be executed at least once, even if TestExpr is initially false.

  • The semicolon is required after the closing parenthesis of the test expression.

The following code shows an example of a do loop:

The do loop

This code produces the following output:

x is 0
x is 1
x is 2

The for Loop

The for loop construct executes the body of the loop as long as the test expression returns true when it is evaluated at the top of the loop. The syntax of the for loop is shown here and illustrated in Figure 9-8.

  • At the beginning of the for loop, Initializer is executed once.

  • TestExpr is then evaluated.

  • If it returns true, Statement is executed, followed by IterationExpr.

  • Control then returns to the top of the loop, and TestExpr is evaluated again.

  • As long as TestExpr returns true, Statement, followed by IterationExpr, will be executed.

  • As soon as TestExpr returns false, execution continues at the statement following Statement.

The for Loop

Some parts of the statement are optional.

  • Initializer, TestExpr, and IterationExpr are all optional. Their positions can be left blank. If the TestExpr position is left blank, the test is assumed to return true. Therefore, there must be some other method of exiting the statement if the program is to avoid going into an infinite loop.

  • The semicolons are required.

The for loop

Figure 9-8. The for loop

Figure 9-8 illustrates the flow of control through the for statement. You should also know the following about its components:

  • Initializer is executed only once, before any other part of the for construct. It is usually used to declare and initialize local values to be used in the loop.

  • TestExpr is evaluated to determine whether Statement should be executed or skipped. It must evaluate to a value of type bool.

  • IterationExpr is executed immediately after Statement, and before returning to the top of the loop to TestExpr.

For example, in the following code:

  • Before anything else, the initializer (int i=0) defines a variable called i, and initializes its value to 0.

  • The condition (i<3) is then evaluated. If it is true, then the body of the loop is executed.

  • At the bottom of the loop, after all the loop statements have been executed, the IterationExpr statement is executed—in this case incrementing the value of i.

// The body of this for loop is executed three times.
for( int i=0 ; i<3 ; i++ )
   Console.WriteLine("Inside loop.  i:  {0}", i);

Console.WriteLine("Out of Loop");

This code produces the following output:

Inside loop.  i:  0
Inside loop.  i:  1
Inside loop.  i:  2
Out of Loop

The Scope of Variables in a for Statement

Any variables declared in the initializer are visible only within the for statement.

  • This is different from C and C++, where the declaration introduces the variable into the enclosing block.

  • The following code illustrates this point:

The Scope of Variables in a for Statement

The local variables declared within the body of the loop are known only within the loop.

Note

Unlike C and C++, the scope of variables declared in the initializer lasts only for the length of the loop.

Multiple Expressions in the Initializer and Iteration Expression

Both the initializer and the iteration expression can contain multiple expressions as long as they are separated by commas.

For example, the following code has two variable declarations in the initializer and two expressions in the iteration expression:

Multiple Expressions in the Initializer and Iteration Expression

This code produces the following output:

0, 10
1, 20
2, 30
3, 40
4, 50

Jump Statements

When the flow of control reaches jump statements, program execution is unconditionally transferred to another part of the program. The jump statements are the following:

  • break

  • continue

  • return

  • goto

  • throw

This chapter covers the first four of these statements. The throw statement is discussed in Chapter 11.

The break Statement

Earlier in this chapter you saw the break statement used in the switch statement. It can also be used in the following statement types as well:

  • for

  • foreach

  • while

  • do

In the body of one of these statements, break causes execution to exit the innermost enclosing statement.

For example, the following while loop would be an infinite loop if it relied only on its test expression, which is always true. But instead, after three iterations of the loop, the break statement is encountered and the loop is exited.

int x = 0;
   while( true )
   {
       x++;
       if( x >= 3 )
           break;
   }

The continue Statement

The continue statement causes program execution to go to the top of the innermost enclosing loop of the following types:

  • while

  • do

  • for

  • foreach

For example, the following for loop is executed five times. In the first three iterations, it encounters the continue statement and goes directly back to the top of the loop, missing the WriteLine statement at the bottom of the loop. Execution only reaches the WriteLine statement during the last two iterations.

for( int x=0; x<5; x++ )            // Execute loop five times
   {
      if( x < 3 )                      // The first three times
         continue;                     // Go directly back to top of loop


      // This line is only reached when x is 3 or greater.
      Console.WriteLine("Value of x is {0}", x);
   }

This code produces the following output:

Value of x is 3
Value of x is 4

The following code shows an example of a continue statement in a while loop. This code produces the same output as the preceding for loop example.

int x = 0;
   while( x < 5 )
   {
      if( x < 3 )
      {
          x++;
          continue;                         // Go back to top of loop
      }


      // This line is reached only when x is 3 or greater.
      Console.WriteLine("Value of x is {0}", x);
      x++;
   }

Labeled Statements

A labeled statement consists of an identifier, followed by a colon, followed by a statement. It has the following form:

Identifier: Statement

A labeled statement is executed exactly as if the label were not there and consisted of just the Statement part.

  • Adding a label to a statement allows control to be transferred to the statement from another part of the code.

  • Labeled statements are only allowed inside blocks.

Labels

Labels have their own declaration space, so the identifier in a labeled statement can be any valid identifier—including those that might be declared in an overlapping scope, such as local variables or parameter names.

For example, the following code shows the valid use of a label with the same identifier as a local variable:

{
      int xyz = 0;                                     // Variable xyz
         ...
      xyz: Console.WriteLine("No problem.");           // Label xyz
   }

There are restrictions, however. The identifier cannot be either

  • The same as another label identifier with an overlapping scope

  • A keyword

The Scope of Labeled Statements

Labeled statements cannot be seen (or accessed) from outside the block in which they are declared. The scope of a labeled statement is

  • The block in which it is declared

  • Any blocks nested inside that block

For example, the code on the left of Figure 9-9 contains several nested blocks, with their scopes marked. There are two labeled statements declared in scope B of the program: increment and end.

  • The shaded portions on the right of the figure show the areas of the code in which the labeled statements are in scope.

  • Code in scope B, and all the nested blocks, can see and access the labeled statements.

  • Code from any of the inner scopes can jump out to the labeled statements.

  • Code from outside (scope A, in this case) cannot jump into a block with a labeled statement.

The scope of labels includes nested blocks.

Figure 9-9. The scope of labels includes nested blocks.

The goto Statement

The goto statement unconditionally transfers control to a labeled statement. Its general form is the following, where Identifier is the identifier of a labeled statement:

goto Identifier ;

For example, the following code shows the simple use of a goto statement:

bool thingsAreFine;
   while (true)
   {
      thingsAreFine = MonitorNuclearReactor();


      if ( thingsAreFine )
         Console.WriteLine("Things are fine.");
      else
         goto NotSoGood;
   }


   NotSoGood: Console.WriteLine("We have a problem.");

The goto statement must be within the scope of the labeled statement.

  • A goto statement can jump to any labeled statement within its own block, or out to any block in which it is nested.

  • A goto statement cannot jump into any blocks nested within its own block.

Warning

Using the goto statement is strongly discouraged, as it can lead to code that is poorly structured, and difficult to debug and maintain. Edsger Dijkstra's 1968 letter to the Communications of the ACM, entitled "Go To Statement Considered Harmful," was an important contribution to computer science; it was one of the first published descriptions of the pitfalls of using the goto statement.

The goto Statement Inside a switch Statement

There are also two other forms of the goto statement, for use inside switch statements. These goto statements transfer control to the correspondingly named switchlabel in the switch statement.

goto case ConstantExpression;
goto default;

The using Statement

Certain types of unmanaged objects are limited in number or expensive with system resources. It is important that when your code is done with them they be released as soon as possible. The using statement helps simplify the process and ensure that these resources are properly disposed of.

A resource is a class or struct that implements the System.IDisposable interface. Interfaces are covered in detail in Chapter 17—but in short, an interface is a collection of unimplemented function members that classes and structs can choose to implement. The IDisposable interface contains a single method named Dispose.

The phases of using a resource are shown in Figure 9-10, and consist of the following:

  • Allocating the resource

  • Using the resource

  • Disposing of the resource

If an unexpected runtime error occurs during the portion of the code using the resource, the code disposing of the resource might not get executed.

Components of using a resource

Figure 9-10. Components of using a resource

Note

The using statement is different than the using directives. The using directives are covered in Chapter 10.

Packaging Use of the Resource

The using statement helps reduce the potential problem of an unexpected runtime error by neatly packaging the use of a resource.

There are two forms of the using statement. The first form is the following, and is illustrated in Figure 9-11.

  • The code between the parentheses allocates the resource.

  • Statement is the code that uses the resource.

  • The using statement implicitly generates the code to dispose of the resource.

Packaging Use of the Resource

Unexpected runtime errors are called exceptions, and are covered in Chapter 11. The standard way of handling the possibility of exceptions is to place the code that might cause an exception in a try block, and place any code that must be executed, whether or not there is an exception, into a finally block.

This form of the using statement does exactly that. It performs the following:

  • Allocates the resource

  • Places Statement in a try block

  • Creates a call to the resource's Dispose method and places it in a finally block.

The effect of the using statement

Figure 9-11. The effect of the using statement

Example of the using Statement

The following code uses the using statement twice--once with a class called TextWriter, and once with a class called TextReader, both from the System.IO namespace. Both classes implement the IDisposable interface, as required by the using statement.

  • The TextWriter resource opens a text file for writing and writes a line to the file.

  • The TextReader resource then opens the same text file, and reads and displays the contents, line by line.

  • In both cases, the using statement makes sure that the objects' Dispose methods are called.

  • Notice also the difference between the using statements in Main and the using directives on the first two lines.

using System;                    // using DIRECTIVE; not using statement
using System.IO;                 // using DIRECTIVE; not using statement


namespace UsingStatement
{
   class Program
   {
      static void Main( )
      {
         // using statement
         using (TextWriter tw = File.CreateText("Lincoln.txt") )
         {
            tw.WriteLine("Four score and seven years ago, ...");
         }


         // using statement
         using (TextReader tr = File.OpenText("Lincoln.txt"))
         {
            string InputString;
            while (null != (InputString = tr.ReadLine()))
               Console.WriteLine(InputString);
         }
      }
   }
}

This code produces the following output:

Four score and seven years ago, ...

Multiple Resources and Nesting

The using statement can also be used with multiple resources of the same type, with the resource declarations separated by commas. The syntax is the following:

Multiple Resources and Nesting

For example, in the following code, each using statement allocates and uses two resources.

static void Main()
   {
      using (TextWriter tw1 = File.CreateText("Lincoln.txt"),
                        tw2 = File.CreateText("Franklin.txt"))
      {
         tw1.WriteLine("Four score and seven years ago, ...");
         tw2.WriteLine("Early to bed; Early to rise ...");
      }


      using (TextReader tr1 = File.OpenText("Lincoln.txt"),
                        tr2 = File.OpenText("Franklin.txt"))
      {
         string InputString;
         while (null != (InputString = tr1.ReadLine()))
            Console.WriteLine(InputString);
         while (null != (InputString = tr2.ReadLine()))
            Console.WriteLine(InputString);
      }
   }

The using statement can also be nested. In the following code, besides the nesting of the using statements, also note that it is not necessary to use a block with the second using statement because it consists of only a single, simple statement.

using ( TextWriter tw1 = File.CreateText("Lincoln.txt") )
   {
      tw1.WriteLine("Four score and seven years ago, ...");


      using ( TextWriter tw2 = File.CreateText("Franklin.txt") ) // Nested
         tw2.WriteLine("Early to bed; Early to rise ...");       // Single
   }

Another Form of the using Statement

Another form of the using statement is the following:

Another Form of the using Statement

In this form, the resource is declared before the using statement.

TextWriter tw = File.CreateText("Lincoln.txt");           // Resource declared

   using ( tw )                                              // using statement
      tw.WriteLine("Four score and seven years ago, ...");

Although this form still ensures that the Dispose method will always be called after you finish using the resource, it does not protect you from attempting to use the resource after the using statement has released its unmanaged resources, leaving it in an inconsistent state. It therefore gives less protection and is discouraged. This form is illustrated in Figure 9-12.

Resource declaration before the using statement

Figure 9-12. Resource declaration before the using statement

Other Statements

There are other statements that are associated with particular features of the language. These statements are covered in the sections dealing with those features. The statements covered in other chapters are shown in Table 9-1.

Table 9-1. Statements Covered in Other Chapters

Statement

Description

Relevant Chapter

checked, unchecked

These statements control the overflow checking context.

Chapter 18

foreach

This statement iterates through each member of a collection.

Chapters 14 and 20

try, throw, finally

These statements are associated with exceptions.

Chapter 11

return

This statement returns control to the calling function member, and can also return a value.

Chapter 5

yield

This statement is used with iterators.

Chapter 20

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

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