Functions comprise statements that execute sequentially in the textual order in which they appear. A statement block is a series of statements appearing between braces (the {} tokens).
A declaration statement declares a new variable, optionally initializing the variable with an expression. A declaration statement ends in a semicolon. You may declare multiple variables of the same type in a comma-separated list. For example:
string someWord = "rosebud"; int someNumber = 42; bool rich = true, famous = false;
A constant declaration is like a variable declaration, except that the variable cannot be changed after it has been declared, and the initialization must occur with the declaration:
const double c = 2.99792458E08; c+=10; // error
The scope of a local or constant variable extends to the end of the current block. You cannot declare another local variable with the same name in the current block or in any nested blocks. For example:
static void Main() { int x; { int y; int x; // Error, x already defined } { int y; // OK, y not in scope } Console.WriteLine(y); // Error, y is out of scope }
Expression statements are expressions that are also valid statements. An expression statement must either change state or call something that might change state. Changing state essentially means changing a variable. The possible expression statements are:
Here are some examples:
// Declare variables with declaration statements: string s; int x, y; System.Text.StringBuilder sb; // Expression statements x = 1 + 2; // Assignment expression x++; // Increment expression y = Math.Max (x, 5); // Assignment expression Console.WriteLine (y); // Method call expression sb = new StringBuilder( ); // Assignment expression new StringBuilder( ); // Object instantiation // expression
When you call a constructor or a method that returns a value, you’re not obliged to use the result. However, unless the constructor or method changes state the statement is completely useless:
new StringBuilder(); // Legal, but does nothing new string ('c', 3); // Legal, but does nothing x.Equals (y); // Legal, but does nothing
C# has the following mechanisms to conditionally control the flow of program execution:
This section covers the simplest two constructs: the if-else
statement and the switch
statement.
An if
statement executes a body of code depending
on whether a bool
expression is true. For
example:
if (5 < 2 * 3) { Console.WriteLine ("true"); // true }
If the body of code is a single statement, you can optionally omit the braces:
if (5 < 2 * 3) Console.WriteLine ("true"); // true
An if
statement is optionally followed by an
else
clause:
if (2 + 2 == 5) Console.WriteLine ("Does not compute"); else Console.WriteLine ("false"); // false
Within an else
clause, you can nest another
if
statement:
if (2 + 2 == 5) Console.WriteLine ("Does not compute"); elseif (2 + 2 == 4)
Console.WriteLine ("Computes"); // Computes
An else
clause always applies to the immediately
preceding if
statement in the statement block. For example:
if (true) if (false) Console.WriteLine( ); else Console.WriteLine("executes");
This is semantically identical to:
if (true) { if (false) Console.WriteLine( ); else Console.WriteLine("executes"); }
We can change the execution flow by moving the braces:
if (true) { if (false) Console.WriteLine( ); } else Console.WriteLine("does not execute");
With braces, you explicitly state your intention. This can improve the readability
of nested if
statements—even when not required by the
compiler. A notable exception is with the following pattern:
static void TellMeWhatICanDo (int age) { if (age >= 35) Console.WriteLine ("You can be president!");else if
(age >= 21) Console.WriteLine ("You can drink!");else if
(age >= 18) Console.WriteLine ("You can vote!"); else Console.WriteLine ("You can wait!"); }
Here, we’ve arranged the if
and else
statements to mimic the “elsif” construct of other
languages (and C#’s #elif
preprocessor directive).
Visual Studio’s auto-formatting recognizes this pattern and preserves the indentation.
Semantically, though, each if
statement following an
else
statement is functionally nested within the
else
statement.
switch
statements let you branch program
execution based on a selection of possible values a
variable may have. switch
statements may result in
cleaner code than multiple if
statements, as switch
statements require an expression to be evaluated only
once. For instance:
static void ShowCard (int cardNumber) { switch (cardNumber) { case 13: Console.WriteLine ("King"); break; case 12: Console.WriteLine ("Queen"); break; case 11: Console.WriteLine ("Jack"); break; case -1: // Joker goto case 12; // Make joker count as queen default: // Executes for any other cardNumber Console.WriteLine (cardNumber); break; } }
You can only switch on an expression of a type that can be statically evaluated,
which restricts it to the primitive types, string
types, and enum
types.
At the end of each case
clause, you must say
explicitly where execution is to go next, with some kind of jump statement. Here are the options:
break
(jumps to the end of the switch
statement)
goto case x
(jumps to another case
clause)
goto default
(jumps to the default
clause)
Any other jump statement—namely, return, throw,
continue
, or goto
label
When more than one value should execute the same code, you can list the common
cases
sequentially:
switch (cardNumber) {case 13:
case 12:
case 11:
Console.WriteLine ("Face card");break; default: Console.WriteLine ("Plain card");break; }
This feature of a switch
statement can be pivotal
in terms of producing cleaner code than multiple if-else
statements.
C# enables a sequence of statements to execute repeatedly with the while, do-while
, and for
statements.
while
loops repeatedly execute a body of code
while a bool
expression is true. The expression is
tested before the body of the loop is executed. For example:
int i = 0; while (i < 3) { Console.Write (i); // 012 i++; }
do-while
loops differ in functionality from
while
loops only in that they test the expression
after the statement block has executed. Here’s the preceding
example rewritten with a do-while
loop:
int i = 0; do { Console.WriteLine(i); i++; } while (i < 3);
for
loops are like while
loops with special clauses for initialization
and iteration of a loop variable. A for
loop contains three clauses as follows:
for (initialization-clause;
condition-clause;
iteration-clause)
statement-or-statement-block
Executes before the loop begins, used to initialize one or more variables
A bool
expression, evaluated before each
loop iteration; if false
, the loop
terminates
Executes after each iteration of the statement block, used typically to update the loop variable
For example, the following prints the numbers 0 through 2:
for (int i = 0; i < 3; i++) Console.WriteLine (i);
Any of the three parts of the for
statement may
be omitted. One can implement an infinite loop such as the following (though while (true)
may be used instead):
for (;;) Console.WriteLine("interrupt me");
The foreach
statement iterates over each element
in an enumerable object. Most of the types in C# and the .NET Framework that represent a
set or list of elements are enumerable. For example, both an array and a string are
enumerable. Here is an example of enumerating over the characters in a string, from the
first character through to the last:
foreach (char c in "beer") Console.Write (c + " "); // b e e r
We define enumerable objects in the upcoming “Enumeration and Iterators” section.
The C# jump
statements are break, continue, goto, return
, and
throw
. Jump statements obey the reliability rules of
try
statements (see the upcoming “try Statements and
Exceptions” section). First, a jump out of a try
block
always executes the try
’s finally
block before reaching the target of the jump. Second, a jump cannot
be made from the inside to the outside of a finally
block.
The break
statement ends the execution of the
body of a while
loop, for
loop, or switch
statement:
int x = 0;
while (true)
{
if (x++ > 5)
break;
// break from the loop
}
// execution continues here after break
...
The continue
statement forgoes the remaining
statements in the loop and makes an early start on the next iteration. The following
loop skips even numbers:
for (int i = 0; i < 10; i++)
{
if ((i % 2) == 0) continue;
Console.Write (i + " "); // 1 3 5 7 9
}
The goto
statement transfers execution to another
label within the statement block. The form is as follows:
goto statement-label;
or, when used within a switch
statement:
goto case case-constant;
A
label
statement is just a placeholder in a code block, denoted with a colon suffix. The
following example iterates the numbers 1 through 5, mimicking a for
loop:
int i = 1;startLoop:
if (i <= 5) { Console.Write (i + " "); // 1 2 3 4 5 i++;goto startLoop;
}
The goto
case statement transfers execution to
another case label in a switch
block (see the earlier
“The switch statement” section).
The lock
statement is a syntactic shortcut for
calling the Enter
and Exit
methods of the Monitor
class, which
provide exclusive locking functionality for multithreaded programs. (For an extensive
online resource on multithreading, see www.albahari.com/threading/.)
The using
statement provides an elegant syntax for
declaring a calling Dispose
on objects that implement
IDisposable
(see “try Statements and Exceptions”
later in the book).