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.
Previous chapters have covered a number of different declaration statements, including declarations of local variables, classes, and class members. This chapter covers 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.
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:
if
part and the else
part of the construct. if( x < y )
; // Empty statement
else
z = a + b; // Simple statement
The previous chapter looked at expressions. Expressions return values, but they can also have 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:
x
. Although this is probably the main reason for the statement, this is considered the side effect.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.
C# provides the flow-of-control constructs common to modern programming languages.
if
if...else
switch
while
do
for
foreach
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 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
.Testexpr
evaluates to true
, Statement
is executed.false
, Statement
is skipped. if( TestExpr )
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 — curly braces needed
y = x + z;
}
int x = 5;
if( x ) // Error: test expression must be a bool, not int
{
...
}
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.
Testexpr
evaluates to true
, Statement1
is executed.false
, Statement2
is executed instead. if( TestExpr )
Statement1
else
Statement2
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 implements multiway branching. Figure 9-3 shows the syntax and structure of the switch
statement.
switch
statement contains zero or more switch sections.Figure 9-3. Structure of a switch statement
Switch labels have the following form:
The flow of control through the structure in Figure 9-3 is the following:
Testexpr
, is evaluated at the top of the construct.break
statement or one of the other four jump statements.
break
, return
, continue
, goto
, and throw
, and they are described later in this chapter.break
statement is the most commonly used for ending a switch
section. The break
statement branches execution to the end of the switch
statement. We’ll cover all the jump statements later in this chapter.Testexpr
is equal to the value ConstExpr1
, the constant expression in the first switch label and then the statements in the statement list following the switch label are executed, until the one of the jump statements is encountered.default
section is optional, but if it is included, it must end with one of the jump statements.Figure 9-4 illustrates the general flow of control through a switch
statement. You can modify the flow through a switch
statement with a goto
statement or a return
statement.
Figure 9-4. The flow of control through a switch statement
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
A switch
statement can have any number of switch sections, including none. 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
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. It must also be of the same type as the test expression
For example, Figure 9-5 shows three sample switch
statements.
Figure 9-5. Switch statements with different types of switch labels
Note Unlike C and C++, each switch
section, including the optional default section, must end with one of the jump statements. In C#, you cannot execute the code in one switch section and then fall through to the next.
Although C# does not allow falling through from one switch section to another, you can do the following:
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 jump 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 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.
Testexpr
is evaluated.Testexpr
evaluates to false
, then execution continues after the end of the while
loop.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
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 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.
Statement
is executed.Testexpr
is evaluated.Testexpr
returns true
, then Statement
is executed again.Testexpr
returns true
, Statement
is executed again.Testexpr
returns false
, control passes to the statement following the end of the loop construct. do
Statement
while( TestExpr ); // End of 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:
Statement
, is always executed at least once, even if Testexpr
is initially false
.The following code shows an example of a do
loop:
This code produces the following output:
x is 0
x is 1
x is 2
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.
for
loop, Initializer
is executed once.Testexpr
is then evaluated.Testexpr
returns true
, Statement
is executed, followed by Iterationexpr
.Testexpr
is evaluated again.Testexpr
returns true
, Statement
, followed by Iterationexpr
, is executed.Testexpr
returns false
, execution continues at the statement following Statement
.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.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:
int i=0
) defines a variable called i
and initializes its value to 0
.i<3
) is then evaluated. If it is true
, then the body of the loop is executed.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
Any variables declared in the initializer are visible only within the 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.
Both the initializer expression 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:
This code produces the following output:
0, 10
1, 20
2, 30
3, 40
4, 50
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 explained in Chapter 11.
Earlier in this chapter you saw the break
statement used in the switch
statement. It can also be used in the following statement types:
for
foreach
while
do
In the body of one of these statements, break
causes execution to exit the innermost enclosing loop.
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 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++;
}
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.
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
Labeled statements cannot be seen (or accessed) from outside the block in which they are declared. The scope of a labeled statement is
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
.
Figure 9-9. The scope of labels includes nested blocks.
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 = GetNuclearReactorCondition();
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.
goto
statement can jump to any labeled statement within its own block or can jump out to any block in which it is nested.goto
statement cannot jump into any blocks nested within its own block. Caution Using the goto
statement is strongly discouraged, because 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.
There are also two other forms of the goto
statement, for use inside switch
statements. These goto
statements transfer control to the correspondingly named switch label in the switch
statement.
goto case ConstantExpression;
goto default;
Certain types of unmanaged objects are limited in number or are expensive with system resources. It's important that when your code is done with them, they be released as soon as possible. The using
statement helps simplify the process and ensures 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:
If an unexpected run-time error occurs during the portion of the code using the resource, the code disposing of the resource might not get executed.
Figure 9-10. Components of using a resource
Note The using
statement is different from the using
directives. The using
directives are covered in Chapter 10.
The using
statement helps reduce the potential problem of an unexpected run-time 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.
Statement
is the code that uses the resource.using
statement implicitly generates the code to dispose of the resource.Unexpected run-time 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:
Statement
in a try
blockDispose
method and places it in a finally
blockFigure 9-11. The effect 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.
TextWriter
resource opens a text file for writing and writes a line to the file.TextReader
resource then opens the same text file and reads and displays the contents, line by line.using
statement makes sure that the objects’ Dispose
methods are called.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, ...
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:
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 is the following:
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.
Figure 9-12. Resource declaration before the using statement
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 , finallu |
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 |
3.139.97.202