Chapter 3. C# Language Fundamentals

Chapter 2 demonstrated a very simple C# program. Nonetheless, that little program was complex enough that we had to skip some of the pertinent details. This chapter illuminates these details by delving more deeply into the syntax and structure of the C# language itself.

In this chapter, I discuss the type system in C#, covering built-in types such as int and bool, and user-defined types (types you create) such as classes, structs, and interfaces. I also cover programming fundamentals, such as how to create and use variables and constants. I’ll then introduce enumerations, strings, identifiers, expressions, and statements.

In the second part of the chapter, I’ll explain and demonstrate the use of flow control statements, using the if, switch, while, do...while, for, and foreach statements. You’ll also learn about operators, including the assignment, logical, relational, and mathematical operators. I’ll finish up with a short tutorial on the C# preprocessor.

Although C# is principally concerned with the creation and manipulation of objects, it is best to start with the fundamental building blocks: the elements from which objects are created. These include the built-in types that are an intrinsic part of the C# language as well as the syntactic elements of C#.

Types

Every variable and object in C# has a “type.” There are built-in types (e.g., int), and you may create your own types (e.g., Employee).

When you create an object, you declare its type, and in a statically typed language such as C#, the compiler will “enforce” that typing, giving you an error at compile time (rather than runtime) if you violate the typing by (for example) trying to assign an employee object to an integer variable. This is a good thing; it cuts down on bugs and makes for more reliable code.

In the vast majority of cases, C# is also “manifestly” typed—which means that you explicitly declare the type of the object. There is one exception, which is the use of the keyword var (covered in Chapter 13). In this case, C# is able to infer the type of the object and thus, rather than being manifest, is actually implicit.

Finally, C# is strongly typed, which means that any operation you attempt on any object or variable must be appropriate to that type, or it will cause a compiler error. Once again, this is a good thing; it helps identify bugs reliably at compile time.

In summary, we can say that C# is statically, manifestly, and strongly typed when using most types, except when using the keyword var, at which time it is statically, implicitly, and strongly typed!

Key to all of this is that it is always statically and strongly typed, which means that you must declare your types, and the compiler will then enforce that you use your objects according to their declared types, and this is a good thing.

The Built-In Types

The C# language itself offers the usual cornucopia of intrinsic (built-in) types you expect in a modern language, each of which maps to an underlying type supported by the .NET CTS. Mapping the C# primitive types to the underlying .NET types ensures that objects you create in C# can be used interchangeably with objects created in any other language compliant with the .NET CTS, such as Visual Basic.

Each built-in type has a specific and unchanging size. Table 3-1 lists many of the built-in types offered by C#.

Table 3-1. C# built-in value types

Type

Size (in bytes)

.NET type

Description

Byte

1

Byte

Unsigned (values 0 to 255)

Char

2

Char

Unicode characters

Bool

1

Boolean

True or false

Sbyte

1

SByte

Signed values (−128 to 127)

Short

2

Int16

Signed short values (−32,768 to 32,767)

Ushort

2

UInt16

Unsigned short values (0 to 65,535)

Int

4

Int32

Signed integer values between −2,147,483,648 and 2,147,483,647

Uint

4

UInt32

Unsigned integer values between 0 and 4,294,967,295

Float

4

Single

Floating-point number. Holds values from approximately +/−1.5 × 10−45 to approximately +/−3.4 × 1038 with seven significant figures

Double

8

Double

Double-precision floating-point. Holds values from approximately +/−5.0 × 10−324 to approximately +/−1.8 × 10308 with 15 to 16 significant figures

decimal

16

Decimal

Fixed-precision value up to 28 digits and the position of the decimal point; this is typically used in financial calculations; requires the suffix “m” or “M”

Long

8

Int64

Signed integers from −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

Ulong

8

UInt64

Unsigned integers ranging from 0 to 0xffffffffffffffff

In addition to these primitive types, C# has two other value types: enum (which I’ll explain later in this chapter) and struct (discussed in Chapter 4).

Choosing a built-in type

Typically, you decide which size integer to use (short, int, or long) based on the magnitude of the value you want to store. For example, a ushort can only hold values from 0 to 65,535, whereas a uint can hold values from 0 to 4,294,967,295.

With that said, memory is fairly cheap, and programmer time is increasingly expensive; so, most of the time you’ll simply declare your variables to be of type int, unless there is a good reason to do otherwise.

When you need to create numbers that represent noninteger values (e.g., 5.7), you’ll choose among float, double, and decimal, depending on the size and degree of precision you need. For most small fractional numbers, float is fine.

Tip

The compiler assumes that any number with a decimal point is a double unless you tell it otherwise. You must therefore use the f suffix for a float, and the m for a decimal, but no other suffixes are required for other types.

To create a float, follow the number with the letter f:

float someFloat = 57f;
;

The char type represents a Unicode character. char literals can be simple, Unicode, or escape characters enclosed by single quote marks. For example, A is a simple character, whereas u0041 is a Unicode character. Escape characters are special two-character tokens that have special meaning to the compiler in which the first character is a backslash. For example, is a horizontal tab. Table 3-2 shows the common escape characters.

Table 3-2. Common escape characters

Char

Meaning

'

Single quote

"

Double quote

\

Backslash

Null

a

Alert



Backspace

f

Form feed

Newline

Carriage return

Horizontal tab

v

Vertical tab

Converting built-in types

Objects of one type can be converted into objects of another type either implicitly or explicitly. Implicit conversions happen automatically; the compiler takes care of it for you.

Implicit conversions happen when you assign a value to a variable of a different type, and the conversion is guaranteed not to lose information. For example, you can implicitly cast from a short (two bytes) to an int (four bytes) by assigning the value in the former to a variable of the latter type. No matter what value is in the short, it is not lost when converted to an int:

Create a variable of type short named x and initialize with the value 5
short x = 5;
// create an integer variable y and initialize with the value held in x
int y = x; // implicit conversion

If you convert the other way, however, you certainly can lose information. If the value in the int is greater than 32,767, it will be truncated in the conversion. The compiler will not perform an implicit conversion from int to short:

short x;
int y = 500;
x = y; // won't compile

Explicit conversions happen when you “cast” a value to a different type. The semantics of an explicit conversion are “Hey! Compiler! I know what I’m doing.” This is sometimes called “hitting it with the big hammer,” and can be very useful or very painful, depending on whether your thumb is in the way of the nail. You must explicitly convert using the cast operator (you place the type you want to convert to in parentheses before the variable you’re converting):

short x;
int y = 500;
x = (short) y; // OK

All the intrinsic types define their own conversion rules.

Tip

At times, it is convenient to define conversion rules for your user-defined types, as I discuss in Chapter 6.

Variables and Constants

A variable is a storage location within a method. In the preceding examples, both x and y are variables. You can assign values to your variables, and you can change those values programmatically.

You create a variable by declaring its type and then giving it a name. You can initialize the variable with a value when you declare it, and you can assign a new value to that variable at any time, changing the value held in the variable. Example 3-1 illustrates this.

Example 3-1. Initializing and assigning a value to a variable
using System;
using System.Collections.Generic;
using System.Text;




namespace InitializingVariables
{
 class Program
 {
    static void Main(string[] args)
    {


    int myInt = 7;
    System.Console.WriteLine("Initialized, myInt: {0}",
      myInt);


    myInt = 5;
    System.Console.WriteLine("After assignment, myInt: {0}",
      myInt);


    }
 }
}


Output:
Initialized, myInt: 7
After assignment, myInt: 5

Tip

Visual Studio creates a namespace and using directive for every program. To save space, I’ve omitted these from most of the code examples after this one.

Here, you initialize the variable myInt to the value 7, display that value, reassign the variable with the value 5, and display it again.

Tip

VB 6 programmers take note: in C#, the datatype comes before the variable name.

Definite Assignment

C# requires definite assignment, and one of the consequences of this requirement is that variables must be initialized or assigned to before they are used. To test this rule, change the line that initializes myInt in Example 3-1 to:

int myInt;

and save the revised program shown in Example 3-2.

Example 3-2. Using an uninitialized variable
using System;


class UninitializedVariable
{
  static void Main(string[] args)
  {
     int myInt;
     System.Console.WriteLine("Uninitialized, myInt: {0}", myInt);
     myInt = 5;
     System.Console.WriteLine("Assigned, myInt: {0}", myInt);

  }
}

When you try to compile this listing, the C# compiler will display an error message, as shown in Figure 3-1.

Error message resulting from using an unassigned variable
Figure 3-1. Error message resulting from using an unassigned variable

Double-clicking the error message will bring you to the problem in the code.

It isn’t legal to use an uninitialized variable in C#. Does this mean you must initialize every variable in a program? In fact, no: you don’t actually need to initialize a variable, but you must assign a value to it before you attempt to use it. Example 3-3 illustrates a correct program.

Example 3-3. Assigning without initializing
using System;


class AssigningWithoutInitializing
{
  static void Main(string[] args)
  {
    int myInt;
    myInt = 7;
    System.Console.WriteLine("Assigned, myInt: {0}", myInt);
    myInt = 5;
    System.Console.WriteLine("Reassigned, myInt: {0}", myInt);


  }
}

Constants

A constant is an object whose value can’t be changed. Variables are a powerful tool, but there are times when you want to use a defined value, one whose value you want to ensure remains constant. For example, you might need to work with the Fahrenheit freezing and boiling points of water in a program simulating a chemistry experiment. Your program will be clearer if you name the variables that store the values FreezingPoint and BoilingPoint, but you don’t want to permit their values to be reassigned. How do you prevent reassignment? The answer is to use a constant.

Constants come in three flavors: literals, symbolic constants, and enumerations. In this assignment:

x = 32;

the value 32 is a literal constant. The value of 32 is always 32. You can’t assign a new value to 32; you can’t make 32 represent the value 99 no matter how you might try.

Symbolic constants assign a name to a constant value. You declare a symbolic constant using the const keyword and the following syntax:

consttype identifier = value;

You must initialize a constant when you declare it, and once initialized, it can’t be altered. For example:

const int FreezingPoint = 32;

In this declaration, 32 is a literal constant, and FreezingPoint is a symbolic constant of type int. Example 3-4 illustrates the use of symbolic constants.

Example 3-4. Using symbolic constants
using System;




namespace SymbolicConstants
{
  class SymbolicConstants
  {
    static void Main(string[] args)
    {
      const int FreezingPoint = 32; // degrees Fahrenheit
      const int BoilingPoint = 212;


      System.Console.WriteLine("Freezing point of water: {0}",
        FreezingPoint);
      System.Console.WriteLine("Boiling point of water: {0}",
        BoilingPoint);//BoilingPoint = 212;


    }
  }
}

Example 3-4 creates two symbolic integer constants: FreezingPoint and BoilingPoint. As a matter of style, constant names are typically written in Pascal notation or all caps, but the language certainly does not require this (see the sidebar, "Camel and Pascal Notation“).

These constants serve the same purpose as always using the literal values 32 and 212 for the freezing and boiling points of water in expressions that require them, but because these constants have names, they convey far more meaning. Also, if you decide to switch this program to Celsius, you can reinitialize these constants at compile time, to 0 and 100, respectively; all the rest of the code ought to continue to work.

To prove to yourself that the constant can’t be reassigned, try to uncomment the last line of the program (shown in bold). When you recompile, you should receive the error shown in Figure 3-2.

Warning that occurs when you try to reassign a constant
Figure 3-2. Warning that occurs when you try to reassign a constant

Enumerations

Enumerations can provide a powerful alternative to constants. An enumeration is a distinct value type, consisting of a set of named constants (called the enumerator list).

In Example 3-4, you created two related constants:

const int FreezingPoint = 32;
const int BoilingPoint = 212;

You might wish to add a number of other useful constants to this list, such as:

const int LightJacketWeather = 60;
const int SwimmingWeather = 72;
const int WickedCold = 0;

This process is somewhat cumbersome, and there is no logical connection between these various constants. C# provides the enumeration to solve these problems:

enum Temperatures
{
  WickedCold = 0,
  FreezingPoint = 32,
  LightJacketWeather = 60,
  SwimmingWeather = 72,
  BoilingPoint = 212,
}

Every enumeration has an underlying type, which can be any integral type (integer, short, long, etc.) except for char. The technical definition of an enumeration is:

[attributes] [modifiers] enum identifier [:base-type] {enumerator-list
};

We consider the optional attributes and modifiers later in this book. For now, just focus on the rest of this declaration. An enumeration begins with the keyword enum, which is generally followed by an identifier, such as:

enum Temperatures

The base type is the underlying type for the enumeration. If you leave out this optional value (and often you will), it defaults to int, but you are free to use any of the integral types (e.g., ushort, long) except for char. For example, the following fragment declares an enumeration of unsigned integers (uint):

enum ServingSizes :uint
{
  Small = 1,
  Regular = 2,
  Large = 3
}

Notice that an enum declaration ends with the enumerator list. The enumerator list contains the constant assignments for the enumeration, each separated by a comma.

Example 3-5 rewrites Example 3-4 to use an enumeration.

Example 3-5. Using enumerations to simplify your code
using System;


namespace EnumeratedConstants
{
  class EnumeratedConstants
  {


    enum Temperatures
    {
      WickedCold = 0,
      FreezingPoint = 32,
      LightJacketWeather = 60,
      SwimmingWeather = 72,
      BoilingPoint = 212,
    }


    static void Main(string[] args)
    {
      System.Console.WriteLine("Freezing point of water: {0}",
        (int)Temperatures.FreezingPoint);
      System.Console.WriteLine("Boiling point of water: {0}",
        (int)Temperatures.BoilingPoint);
    }
  }
}

As you can see, an enum must be qualified by its identifier (e.g., Temperatures.WickedCold). By default, an enumeration value is displayed using its symbolic name (such as BoilingPoint or FreezingPoint). When you want to display the value of an enumerated constant, you must cast the constant to its underlying type (int). The integer value is passed to WriteLine, and that value is displayed.

Each constant in an enumeration corresponds to a numerical value—in this case, an integer. If you don’t specifically set it otherwise, the enumeration begins at 0, and each subsequent value counts up from the previous one.

If you create the following enumeration:

enum SomeValues
{
  First,
  Second,
  Third = 20,
  Fourth
}

the value of First will be 0, Second will be 1, Third will be 20, and Fourth will be 21.

An explicit conversion is required to convert between an enum type and an integral type.

Strings

It is nearly impossible to write a C# program without creating strings. A string object holds a series of characters.

You declare a string variable using the string keyword much as you would create an instance of any object:

string myString;

You create a string literal by placing double quotes around a string of letters:

"Hello World"

It is common to initialize a string variable with a string literal:

string myString = "Hello World";

We cover strings in much greater detail in Chapter 10.

Identifiers

An identifier is just the name the programmer chooses for the types, methods, variables, constants, objects, and so on in the program. An identifier must begin with a letter or an underscore, and remember that identifiers are case-sensitive, so C# treats someName and SomeName as two different identifiers.

Tip

It is normally not good programming practice to create two variables or classes with names that are differentiated only by capitalization. Although the compiler will not be confused, the programmer will be, and the cost of attempting to maintain such a program can be very high.

The exception to this is the common practice of having a member variable (explained in Chapter 4) and a property with the same name, differentiated only by using camel notation for the former, and Pascal notation for the latter.

The Microsoft naming conventions suggest using camel notation (initial lowercase, such as someVariable) for variable names, and Pascal notation (initial uppercase, such as SomeMethodOrProperty) for method names and most other identifiers.

Tip

Microsoft recommends against Hungarian notation (e.g., iSomeInteger) and underscores (e.g., Some_Value). Microsoft’s Charles Simonyi (who was born September 10, 1948, in Budapest) invented Hungarian notation, and it was very useful when languages were limited to a small number of types.

Along with nearly 2 billion other interesting articles, Wikipedia (http://en.wikipedia.org) provides extensive articles on Hungarian notation, on Charles Simonyi, and on Richard Dawkins, who holds the Charles Simonyi Chair for Public Understanding of Science at Oxford University.

Whitespace

In the C# language, spaces, tabs, and newlines are considered to be “whitespace” (so named because you see only the white of the underlying “page”). Extra whitespace is generally ignored in C# statements. You can write:

myVariable = 5;

or:

myVariable      =                5;

and the compiler will treat the two statements as identical.

The key word in the preceding rule is “extra” whitespace. Some whitespace is not extra; it is required to allow the compiler to differentiate one word from another. Thus, if you were to enter:

int myVariable = 5; // no problem

or:

int myVariable=5; // no problem

both would compile, because the spaces between the identifier myVariable, the assignment operator (=), and the literal value 5 are “extra.” If, however, you were to enter:

intMyVariable=5;  // error

you would receive a compiler error, because the space between the keyword int and the identifier myVariable is not extra, it is required.

Another exception to the “whitespace is ignored” rule is within strings. If you write:

Console.WriteLine("Hello World");

each space between “Hello” and “World” is treated as another character in the string.

Most of the time, the use of whitespace is intuitive. The key is to use whitespace to make the program more readable to the programmer; the compiler is typically indifferent.

Tip

VB programmers take note: in C# the end-of-line has no special significance; you end statements with semicolons, not newline characters. There is no line-continuation character because none is needed.

Statements

In C#, a complete program instruction is called a statement. Programs consist of sequences of C# statements. Virtually every statement ends with a semicolon (;). For example:

int x; // a statement
x = 23; // another statement
int y = x; // yet another statement

C# statements are evaluated in order. The compiler starts at the beginning of a statement list and makes its way to the end. This would be entirely straightforward, and terribly limiting, were it not for branching. There are two types of branches in a C# program: unconditional branches and conditional branches.

Program flow is also affected by looping and iteration statements, which are signaled by the keywords for, while, do, in, and foreach. I discuss iteration later in this chapter. For now, let’s consider some of the more basic methods of conditional and unconditional branching.

Unconditional Branching Statements

You can create an unconditional branch in one of two ways. The first way is by invoking a method. When the compiler encounters the name of a method, it stops execution in the current method and branches to the newly “called” method. When that method returns a value, execution picks up in the original method on the line just below the method call. Example 3-6 illustrates.

Example 3-6. Calling a method
using System;




namespace CallingAMethod
{
  class CallingAMethod
  {
    static void Main(  )
    {
      Console.WriteLine("In Main! Calling SomeMethod(  )...");
      SomeMethod(  );
      Console.WriteLine("Back in Main(  ).");
    }
    static void SomeMethod(  )
    {
      Console.WriteLine("Greetings from SomeMethod!");
    }
  }
}
Output:
In Main! Calling SomeMethod(  )...
Greetings from SomeMethod!
Back in Main(  ).

Program flow begins in Main( ) and proceeds until SomeMethod( ) is invoked (invoking a method is also referred to as “calling” the method). At that point, program flow branches to the method. When the method completes, program flow resumes at the next line after the call to that method.

The second way to create an unconditional branch is with one of the unconditional branch keywords: goto, break, continue, return, or throw. I provide additional information about the first three jump statements later in this chapter. The return statement returns control to the calling method. I discuss the final statement, throw, in Chapter 11.

Conditional Branching Statements

A conditional branch is created by a conditional statement, which is signaled by a keyword such as if, else, or switch. A conditional branch occurs only if the condition expression evaluates true.

Tip

C and C++ programmers take note: unlike C and C++, in which any expression can be used in a conditional, C# requires that all conditional expressions evaluate to a Boolean value.

if...else statements

if...else statements branch based on a condition. The condition is an expression, tested in the head of the if statement. If the condition evaluates true, the statement (or block of statements) in the body of the if statement is executed.

if statements may contain an optional else statement. The else statement is executed only if the expression in the head of the if statement evaluates false:

if (expression)
  statement1[else
  statement2]

This is the kind of if statement description you are likely to find in your compiler documentation. It shows you that the if statement takes a Boolean expression (an expression that evaluates true or false) in parentheses, and executes statement1 if the expression evaluates true. Note that statement1 can actually be a block of statements within braces.

You can also see that the else statement is optional, as it is enclosed in square brackets.

Tip

Square brackets are used in the documentation to indicate that the expression is optional. Parentheses (in the if statement) are not part of the documentation, they are actually required in the code.

Although this gives you the syntax of an if statement, an illustration will make its use clear. See Example 3-7.

Example 3-7. if...else statements
using System;
class Values
{
  static void Main(  )
  {
    int valueOne = 10;
    int valueTwo = 20;


    if ( valueOne > valueTwo )
    {
      Console.WriteLine(
      "ValueOne: {0} larger than ValueTwo: {1}",
      valueOne, valueTwo);
    }
    else
    {
      Console.WriteLine(
      "ValueTwo: {0} larger than ValueOne: {1}",
      valueTwo,valueOne);
    }


    valueOne = 30; // set valueOne higher


    if ( valueOne > valueTwo )
    {
      valueTwo = valueTwo = valueOne++;


      Console.WriteLine("
Setting valueTwo to valueOne value, ");
      Console.WriteLine("and incrementing ValueOne.
");
      Console.WriteLine("ValueOne: {0} ValueTwo: {1}",
      valueOne, valueTwo);
    }
    else
    {
      valueOne = valueTwo;
      Console.WriteLine("Setting them equal. ");
      Console.WriteLine("ValueOne: {0} ValueTwo: {1}",
      valueOne, valueTwo);
    }
  }
}

In Example 3-7, the first if statement tests whether valueOne is greater than valueTwo. The relational operators such as greater than (>), less than (<), and equal to (==) are fairly intuitive to use.

The test of whether valueOne is greater than valueTwo evaluates false (because valueOne is 10 and valueTwo is 20, so valueOne is not greater than valueTwo). The else statement is invoked, printing the statement:

ValueTwo: 20 is larger than ValueOne: 10

The second if statement evaluates true and all the statements in the if block are evaluated, causing two lines to print:

Setting valueTwo to valueOne value,
and incrementing ValueOne.


ValueOne: 30 ValueTwo: 31

Nested if statements

It is possible, and not uncommon, to nest if statements to handle complex conditions. For example, suppose you need to write a program to evaluate the temperature, and specifically to return the following types of information:

  • If the temperature is 32 degrees or lower, the program should warn you about ice on the road.

  • If the temperature is exactly 32 degrees, the program should tell you that there may be ice patches.

There are many good ways to write this program. Example 3-8 illustrates one approach, using nested if statements.

Example 3-8. Nested if statements
using System;
using System.Collections.Generic;
using System.Text;


namespace NestedIf
{
  class NestedIf
  {
    static void Main(  )
    {
      int temp = 32;


      if ( temp <= 32 )
      {
        Console.WriteLine( "Warning! Ice on road!" );
        if ( temp == 32 )
        {
          Console.WriteLine(
          "Temp exactly freezing, beware of water." );
        }
        else
        {
          Console.WriteLine( "Watch for black ice! Temp: {0}", temp );
        } // end else
      } // end if (temp <= 32)
    } // end main
  } // end class
} // end namespace

The logic of Example 3-8 is that it tests whether the temperature is less than or equal to 32. If so, it prints a warning:

if (temp <= 32)
{
  Console.WriteLine("Warning! Ice on road!");

The program then checks whether the temp is equal to 32 degrees. If so, it prints one message; if not, the temp must be less than 32, and the program prints the second message. Notice that this second if statement is nested within the first if, so the logic of the else is “since it has been established that the temp is less than or equal to 32, and it isn’t equal to 32, it must be less than 32.”

switch statements: an alternative to nested ifs

Nested if statements can be hard to read, hard to get right, and hard to debug when used to excess (do not operate heavy machinery when using more than six).

When you have a complex set of choices to make, the switch statement may be a more readable alternative. The logic of a switch statement is “pick a matching value and act accordingly”:

switch (expression)
{
  case constant-expression:
   statement    jump-statement  [default: statement]xs
}

As you can see, like an if statement, the expression is put in parentheses in the head of the switch statement. Each case statement then requires a constant expression; that is, a literal or symbolic constant or an enumeration. If a case is matched, the statement(s) associated with that case is executed. This must be followed by a jump statement. Typically, the jump statement is break, which transfers execution out of the switch. An alternative is a goto statement, typically used to jump into another case, as Example 3-9 illustrates.

Example 3-9. The switch statement
using System;
class SwitchStatement
{
    enum Party
    {
        Democrat,
        ConservativeRepublican,
        Republican,
        Libertarian,
        Liberal,
        Progressive,
    };
  static void Main(string[] args)
    {
        Party myChoice = Party.Libertarian;


        switch (myChoice)
        {
            case Party.Democrat:
                Console.WriteLine("You voted Democratic.
");
                break;
            case Party.ConservativeRepublican: // fall through
            //Console.WriteLine(
            //"Conservative Republicans are voting Republican
");
            case Party.Republican:
                Console.WriteLine("You voted Republican.
");
                break;
            case Party.Liberal:
                Console.WriteLine(" Liberal is now Progressive");
                goto case Party.Progressive;
            case Party.Progressive:
                Console.WriteLine("You voted Progressive.
");
                break;
            case Party.Libertarian:
                Console.WriteLine("Libertarians are voting Democratic");
                goto case Party.Democrat;
            default:
                Console.WriteLine("You did not pick a valid choice.
");
                break;
        }


        Console.WriteLine("Thank you for voting.");
    }
}

In this whimsical example, we create constants for various political parties. We then assign one value (Libertarian) to the variable myChoice and switch according to that value. If myChoice is equal to Democrat, we print out a statement. Notice that this case ends with break. break is a jump statement that takes us out of the switch statement and down to the first line after the switch, on which we print, “Thank you for voting.”

Tip

VB 6 programmers take note: the equivalent of the C# switch statement is the VB 6 Select Case statement. Also, whereas VB 6 allows you to test a range of values using a single Case statement, C# syntax doesn’t provide for this contingency. The following two Case statements are syntactically correct in VB 6:

Case Is > 100
Case 50 to 60

However, these statements aren’t valid in C#. In C#, you can test only a single constant expression. To test a range, you must test each value independently and “fall through” to a common case block.

The value ConservativeRepublican has no statement under it, and it “falls through” to the next statement: Republican. If the value is ConservativeRepublican or Republican, the Republican statements execute. You can “fall through” in this way only if there is no body within the statement. If you uncomment WriteLine( ) under LiberalRepublican, this program won’t compile.

Tip

C and C++ programmers take note: you can’t fall through to the next case unless the case statement is empty. Thus, you can write this:

case 1: // fall through ok (no statement for case 1)
case 2:

You can’t, however, write this:

case 1:
  TakeSomeAction(  );
    // fall through not OK, case 1 not empty
case 2:

Here, case 1 has a statement in it, and you can’t fall through. If you want case 1 to fall through to case 2, you must explicitly use goto:

case 1:
 TakeSomeAction(  );
 goto case 2; // explicit fall through
case 2:

If you do need a statement, but then you want to execute another case, you can use the goto statement as shown in the Liberal case:

goto case Progressive;

It is not required that the goto take you to the next case statement. For instance, in the next example, the Libertarian choice also has a goto, but this time it jumps all the way back up to the Democrat case. Because our value was set to Libertarian, this is just what occurs. We print out the Libertarian statement, go to the Democrat case, print that statement, and then hit the break, taking us out of the switch and down to the final statement. The output for all of this is:

Libertarians are voting Democrat now.
You voted Democrat.


Thank you for voting.

Note the default case, excerpted from Example 3-9:

default:
 Console.WriteLine(
 "You did not pick a valid choice.
");

If none of the cases match, the default case will be invoked, warning the user of the mistake.

Switch on string statements

In the previous example, the switch value was an integral constant. C# offers the ability to switch on a string, allowing you to write:

case "Libertarian":

If the strings match, the case statement is entered.

Iteration Statements

C# provides an extensive suite of iteration statements, including for, while, and do...while loops, as well as foreach loops (new to the C family, but familiar to VB programmers). In addition, C# supports the goto, break, continue, and return jump statements.

The goto statement

The goto statement is the seed from which all other iteration statements have been germinated. Unfortunately, it is a semolina seed, producer of spaghetti code and endless confusion. Most experienced programmers properly shun the goto statement, but in the interest of completeness, here’s how you use it:

  1. Create a label.

  2. goto that label.

The label is an identifier followed by a colon. The goto command is typically tied to a condition, as Example 3-10 illustrates.

Example 3-10. Using goto
#region Using directives


using System;
using System.Collections.Generic;
using System.Text;


#endregion


namespace UsingGoTo
{
  class UsingGoTo
  {
    static void Main( string[] args )
    {
      int i = 0;
      repeat: // the label
      Console.WriteLine( "i: {0}", i );
      i++;
      if ( i < 10 )
      goto repeat; // the dastardly deed
      return;
    }
  }
}

If you were to try to draw the flow of control in a program that makes extensive use of goto statements, the resulting morass of intersecting and overlapping lines might look like a plate of spaghetti; hence the term “spaghetti code.” It was this phenomenon that led to the creation of alternatives such as the while loop. Many programmers feel that using goto in anything other than a trivial example creates confusion and difficult-to-maintain code.

The while loop

The semantics of the while loop are “while this condition is true, do this work.” The syntax is:

while (expression) statement

As usual, an expression is any statement that returns a value. While statements require an expression that evaluates to a Boolean (true/false) value, and that statement can, of course, be a block of statements. Example 3-11 updates Example 3-10, using a while loop.

Example 3-11. Using a while loop
#region Using directives


using System;
using System.Collections.Generic;
using System.Text;


#endregion


namespace WhileLoop
{
  class WhileLoop
  {
    static void Main( string[] args )
    {
       int i = 0;
       while ( i < 10 )
      {
        Console.WriteLine( "i: {0}", i );
        i++;
      }
      return;
    }
  }
}

The code in Example 3-11 produces results identical to the code in Example 3-10, but the logic is a bit clearer. The while statement is nicely self-contained, and it reads like an English sentence: “while i is less than 10, print this message and increment i.”

Notice that the while loop tests the value of i before entering the loop. This ensures that the loop will not run if the condition tested is false; thus, if i is initialized to 11, the loop will never run.

The do...while loop

A while statement will never execute if the condition tested returns false. If you want to ensure that your statement is run at least once, use a do...while loop:

do statement while (expression);

An expression is any statement that returns a value. Example 3-12 shows the do...while loop.

Example 3-12. The do...while loop
#region Using directives


using System;
using System.Collections.Generic;
using System.Text;


#endregion


namespace DoWhile
{
  class DoWhile
  {
    static int Main( string[] args )
    {
      int i = 11;
      do
      {
        Console.WriteLine( "i: {0}", i );
        i++;
      } while ( i < 10 );
      return 0;
    }
  }
}

Here, i is initialized to 11 and the while test fails, but only after the body of the loop has run once.

The for loop

A careful examination of the while loop in Example 3-11 reveals a pattern often seen in iterative statements: initialize a variable (i = 0), test the variable (i < 10), execute a series of statements, and increment the variable (i++). The for loop allows you to combine all these steps in a single loop statement:

for ([initializers]; [expression]; [iterators]) statement

Example 3-13 illustrates the for loop.

Example 3-13. The for loop
using System;
using System.Collections.Generic;
using System.Text;




namespace ForLoop
{
  class ForLoop
  {
    static void Main( string[] args )
    {
      for ( int i = 0; i < 100; i++ )
      {
        Console.Write( "{0} ", i );


        if ( i % 10 == 0 )
        {
          Console.WriteLine( "	{0}", i );
        }
      }
      return ;
    }
  }
}


Output:
0 0
1 2 3 4 5 6 7 8 9 10 10
11 12 13 14 15 16 17 18 19 20 20
21 22 23 24 25 26 27 28 29 30 30
31 32 33 34 35 36 37 38 39 40 40
41 42 43 44 45 46 47 48 49 50 50
51 52 53 54 55 56 57 58 59 60 60
61 62 63 64 65 66 67 68 69 70 70
71 72 73 74 75 76 77 78 79 80 80
81 82 83 84 85 86 87 88 89 90 90
91 92 93 94 95 96 97 98 99

This for loop makes use of the modulus operator described later in this chapter. The value of i is printed until i is a multiple of 10:

if ( i % 10 == 0)

A tab is then printed, followed by the value. Thus, the 10s (20, 30, 40, etc.) are called out on the right side of the output.

Tip

VB 6 programmers take note: in C#, looping variables are declared within the header of the for or foreach statement (rather than before the statement begins). This means that they are in scope only within the block, and you can’t refer to them outside the loop. I cover the foreach statement in detail in Chapter 9.

The individual values are printed using Console.Write( ), which is much like WriteLine( ), but which doesn’t enter a newline, allowing the subsequent writes to occur on the same line.

A few quick points to notice: in a for loop, the condition is tested before the statements are executed. Thus, in the example, i is initialized to 0, and then it is tested to see whether it is less than 100. Because i < 100 returns true, the statements within the for loop are executed. After the execution, i is incremented (i++).

Note that the variable i is scoped to within the for loop (i.e., the variable i is visible only within the for loop). Example 3-14 will not compile.

Example 3-14. Scope of variables declared in a for loop
#region Using directives


using System;
using System.Collections.Generic;
using System.Text;


#endregion


namespace ForLoopScope
{
  class ForLoopScope
  {
    static void Main( string[] args )
    {
      for ( int i = 0; i < 100; i++ )
      {
        Console.Write( "{0} ", i );


        if ( i % 10 == 0 )
        {
          Console.WriteLine( "	{0}", i );
        }
      }Console.WriteLine( "
 Final value of i: {0}", i );
    }
  }
}

The line shown in bold fails, as the variable i is not available outside the scope of the for loop itself.

The foreach statement

The foreach statement is new to the C family of languages; it is used for looping through the elements of an array or a collection. I defer discussion of this incredibly useful statement until Chapter 9.

The continue and break statements

There are times when you would like to return to the top of a loop without executing the remaining statements in the loop. The continue statement causes the loop to skip the remaining steps in the loop.

The other side of that coin is the ability to break out of a loop and immediately end all further work within the loop. For this purpose, the break statement exists.

Example 3-15 illustrates the mechanics of continue and break. This code, suggested to us by one of our technical reviewers, is intended to create a traffic signal processing system. The signals are simulated by entering numerals and uppercase characters from the keyboard, using Console.ReadLine( ), which reads a line of text from the keyboard.

The algorithm is simple: receipt of a 0 (zero) means normal conditions, and no further action is required except to log the event. (In this case, the program simply writes a message to the console; a real application might enter a timestamped record in a database.) On receipt of an abort signal (here simulated with an uppercase A), the problem is logged and the process is ended. Finally, for any other event, an alarm is raised, perhaps notifying the police. (Note that this sample doesn’t actually notify the police, though it does print out a harrowing message to the console.) If the signal is X, the alarm is raised, but the while loop is also terminated.

Example 3-15. Using continue and break
#region Using directives


using System;
using System.Collections.Generic;
using System.Text;


#endregion


namespace ContinueBreak
{
  class ContinueBreak
  {
    static void Main( string[] args )
    {
      string signal = "0"; // initialize to neutral
      while ( signal != "X" ) // X indicates stop
      {
        Console.Write( "Enter a signal: " );
        signal = Console.ReadLine(  );


        // do some work here, no matter what signal you
        // receive
        Console.WriteLine( "Received: {0}", signal );


        if ( signal == "A" )
        {
          // faulty - abort signal processing
          // Log the problem and abort.
          Console.WriteLine( "Fault! Abort
" );
          break;
        }


        if ( signal == "0" )
        {
          // normal traffic condition
          // log and continue on
          Console.WriteLine( "All is well.
" );
          continue;
        }



 // Problem. Take action and then log the problem
        // and then continue on
        Console.WriteLine( "{0} -- raise alarm!
",
        signal );
      } // end while
    } // end main
  } // end class
} // end namespace


Output:
Enter a signal: 0
Received: 0
All is well.


Enter a signal: B
Received: B
B -- raise alarm!


Enter a signal: A
Received: A
Fault! Abort

The point of this exercise is that when the A signal is received, the action in the if statement is taken, and then the program breaks out of the loop without raising the alarm. When the signal is 0, it is also undesirable to raise the alarm, so the program continues from the top of the loop.

Operators

An operator is a symbol that causes C# to take an action. The C# primitive types (e.g., int) support a number of operators such as assignment, increment, and so forth.

The Assignment Operator (=)

The = symbol causes the operand on the left side of the operator to have its value changed to whatever is on the right side of the operator. Statements that evaluate to a value are called expressions. You may be surprised how many statements do evaluate to a value. For example, an assignment such as:

myVariable = 57;

is an expression; it evaluates to the value assigned, which, in this case, is 57.

Note that the preceding statement assigns the value 57 to the variable myVariable. The assignment operator (=) doesn’t test equality; rather, it causes whatever is on the right side (57) to be assigned to whatever is on the left side (myVariable).

Warning

VB programmers take note: C# distinguishes between equality (two equals signs) and assignment (one equals sign).

Because myVariable = 57 (read aloud as “assign the numeric value 57 to the variable whose name is myVariable”) is an expression that evaluates to 57, it can be used as part of another assignment operator, such as:

mySecondVariable = myVariable = 57;

In this statement, the literal value 57 is assigned to the variable myVariable. The value of that assignment (57) is then assigned to the second variable, mySecondVariable. Thus, the value 57 is assigned to both variables.

Tip

The value 57 is referred to as a literal value (as opposed to a symbolic value). A symbolic value is one that is housed in a variable, a constant, or an expression. A literal value is the value itself, written in the conventional way.

You can therefore initialize any number of variables to the same value with one statement:

a = b = c = d = e = 20;

Mathematical Operators

C# uses five mathematical operators: four for standard calculations, and a fifth to return the remainder in integer division. The following sections consider the use of these operators.

Simple arithmetical operators (+, −, *, /)

C# offers operators for simple arithmetic: the addition (+), subtraction (-), multiplication (*), and division (/) operators work as you might expect, with the possible exception of integer division.

When you divide two integers, C# divides like a child in fourth grade: it throws away any fractional remainder. Thus, dividing 17 by 4 returns the value 4 (17/4 = 4, with a remainder of 1). C# provides a special operator (modulus, %, which I describe in the next section) to retrieve the remainder.

Note, however, that C# does return fractional answers when you divide floats, doubles, and decimals.

The modulus operator (%) to return remainders

To find the remainder in integer division, use the modulus operator (%). For example, the statement 17%4 returns 1 (the remainder after integer division).

The modulus operator turns out to be more useful than you might at first imagine. When you perform modulus n on a number that is a multiple of n, the result is 0. Thus, 80%10 = 0 because 80 is an exact multiple of 10. This fact allows you to set up loops in which you take an action every nth time through the loop by testing a counter to see whether %n is equal to 0. This strategy comes in handy in the use of the for loop, as I described earlier in this chapter. Example 3-16 illustrates the effects of division on integers, floats, doubles, and decimals.

Example 3-16. Division and modulus
#region Using directives


using System;
using System.Collections.Generic;
using System.Text;


#endregion


namespace DivisionModulus
{
  class DivisionModulus
  {
    static void Main( string[] args )
    {
      int i1, i2;
      float f1, f2;
      double d1, d2;
      decimal dec1, dec2;


      i1 = 17;
      i2 = 4;
      f1 = 17f;
      f2 = 4f;
      d1 = 17;
      d2 = 4;
      dec1 = 17;
      dec2 = 4;
      Console.WriteLine( "Integer:	{0}
float:		{1}",
        i1 / i2, f1 / f2 );
      Console.WriteLine( "double:		{0}
decimal:	{1}",
        d1 / d2, dec1 / dec2 );
      Console.WriteLine( "
Modulus:	{0}", i1 % i2 );


    }
  }
}


Output:
Integer:     4
float:       4.25
double:      4.25
decimal:     4.25


Modulus:     1

Now, consider this line from Example 3-16:

Console.WriteLine("Integer:	{0}
float:		{1}",
 i1/i2, f1/f2);

It begins with a call to Console.WriteLine( ), passing in this partial string:

"Integer:	{0}

This will print the characters Integer: followed by a tab ( ), followed by the first parameter ({0}), and then followed by a newline character ( ). The next string snippet:

float:		{1}

is very similar. It prints float: followed by two tabs (to ensure alignment), the contents of the second parameter ({1}), and then another newline. Notice the subsequent line, as well:

Console.WriteLine("
Modulus:	{0}", i1%i2);

This time, the string begins with a newline character, which causes a line to be skipped just before the string Modulus: is printed. You can see this effect in the output.

Increment and Decrement Operators

A common requirement is to add a value to a variable, subtract a value from a variable, or otherwise change the mathematical value, and then to assign that new value back to the same variable. You might even want to assign the result to another variable altogether. The following two sections discuss these cases respectively.

Calculate and reassign operators

Suppose you want to increment the mySalary variable by 5,000. You can do this by writing:

mySalary = mySalary + 5000;

The addition happens before the assignment, and it is perfectly legal to assign the result back to the original variable. Thus, after this operation completes, mySalary will have been incremented by 5,000. You can perform this kind of assignment with any mathematical operator:

mySalary = mySalary * 5000;
mySalary = mySalary - 5000;

and so forth.

The need to increment and decrement variables is so common that C# includes special operators for self-assignment. Among these operators are +=, −=, *=, /=, and %=, which, respectively, combine addition, subtraction, multiplication, division, and modulus with self-assignment. Thus, you can alternatively write the previous examples as:

mySalary += 5000;
mySalary *= 5000;
mySalary −= 5000;

The effect of this is to increment mySalary by 5,000, multiply mySalary by 5,000, and subtract 5,000 from the mySalary variable, respectively.

Because incrementing and decrementing by 1 is a very common need, C# (like C and C++ before it) also provides two special operators. To increment by 1, you use the ++ operator, and to decrement by 1, you use the -- operator.

Thus, if you want to increment the variable myAge by 1, you can write:

myAge++;

The prefix and postfix operators

To complicate matters further, you might want to increment a variable and assign the results to a second variable:

firstValue = secondValue++;

The question arises: do you want to assign before you increment the value, or after? In other words, if secondValue starts out with the value 10, do you want to end with firstValue and secondValue equal to 11, or do you want firstValue to be equal to 10 (the original value), and secondValue to be equal to 11?

C# (again, like C and C++) offers two flavors of the increment and decrement operators: prefix and postfix. Thus, you can write:

firstValue = secondValue++; // postfix

which will assign first, and then increment (firstValue=10, secondValue=11). You can also write:

firstValue = ++secondValue; // prefix

which will increment first, and then assign (firstValue=11, secondValue=11).

It is important to understand the different effects of prefix and postfix, as illustrated in Example 3-17.

Example 3-17. Prefix versus postfix increment
#region Using directives


using System;
using System.Collections.Generic;
using System.Text;


#endregion


namespace PrefixPostfix
{
  class PrefixPostfix
  {
    static void Main( string[] args )
    {
      int valueOne = 10;
      int valueTwo;
      valueTwo = valueOne++;
      Console.WriteLine( "After postfix: {0}, {1}", valueOne,
        valueTwo );
      valueOne = 20;
      valueTwo = ++valueOne;
      Console.WriteLine( "After prefix: {0}, {1}", valueOne,
        valueTwo );


      }
   }
}


Output:
After postfix: 11, 10
After prefix: 21, 21

Relational Operators

Relational operators are used to compare two values, and then return a Boolean (true or false). The greater-than operator (>), for example, returns true if the value on the left of the operator is greater than the value on the right. Thus, 5 > 2 returns the value true, whereas 2 > 5 returns the value false.

Table 3-3 shows the relational operators for C#. This table assumes two variables: bigValue and smallValue, in which bigValue has been assigned the value 100 and smallValue the value 50.

Table 3-3. C# relational operators (assumes bigValue = 100 and smallValue = 50)

Name

Operator

Given this statement

The expression evaluates to

Equals

==

bigValue == 100

bigValue == 80

true

false

Not equals

!=

bigValue != 100

bigValue != 80

false

true

Greater than

>

bigValue > smallValue

true

Greater than or equals

>=

bigValue >= smallValue

smallValue >= bigValue

true

false

Less than

<

bigValue < smallValue

false

Less than or equals

<=

smallValue <= bigValue

bigValue <= smallValue

true

false

Each relational operator acts as you might expect. However, take note of the equals operator (==), which is created by typing two equals signs (=) in a row (i.e., without any space between them); the C# compiler treats the pair as a single operator.

The C# equals operator (==) tests for equality between the objects on either side of the operator. This operator evaluates to a Boolean value (true or false). Thus, the expression:

myX == 5

evaluates to true if and only if myX is a variable whose value is 5.

Use of Logical Operators with Conditionals

If statements (discussed earlier in this chapter) test whether a condition is true. Often, you will want to test whether two conditions are true, or whether only one is true or none is true. C# provides a set of logical operators for this, as shown in Table 3-4. This table assumes two variables, x and y, in which x has the value 5 and y the value 7.

Table 3-4. C# logical operators (assumes x = 5, y = 7)

Name

Operator

Given this statement

The expression evaluates to

and

&&

(x == 3) && (y == 7)

false

or

||

(x == 3) || (y == 7)

true

not

!

! (x == 3)

true

The and operator (&&) tests whether two statements are both true. The first line in Table 3-4 includes an example that illustrates the use of the and operator:

(x == 3) && (y == 7)

The entire expression evaluates false because one side (x == 3) is false.

With the or operator (||), either or both sides must be true; the expression is false only if both sides are false. So, in the case of the example in Table 3-4:

(x == 3) || (y == 7)

the entire expression evaluates true because one side (y==7) is true.

With a not operator (!), the statement is true if the expression is false, and vice versa. So, in the accompanying example:

! (x == 3)

the entire expression is true because the tested expression (x==3) is false. (The logic is “it is true that it is not true that x is equal to 3.”)

Operator Precedence

The compiler must know the order in which to evaluate a series of operators. For example, if we write:

myVariable = 5 + 7 * 3;

there are three operators for the compiler to evaluate (=, +, and *). It could, for example, operate left to right, which would assign the value 5 to myVariable, then add 7 to the 5 (12) and multiply by 3 (36)—but, of course, then it would throw that 36 away. This is clearly not what is intended.

The rules of precedence tell the compiler which operators to evaluate first. As is the case in algebra, multiplication has higher precedence than addition, so 5+7*3 is equal to 26 rather than 36. Both addition and multiplication have higher precedence than assignment, so the compiler will do the math, and then assign the result (26) to myVariable only after the math is completed.

In C#, parentheses are also used to change the order of precedence much as they are in algebra. Thus, you can change the result by writing:

myVariable = (5+7) * 3;

Grouping the elements of the assignment in this way causes the compiler to add 5+7, multiply the result by 3, and then assign that value (36) to myVariable. Table 3-5 summarizes operator precedence in C#, listing the operators with the topmost layers being evaluated before the layers that come below.

Table 3-5. Operator precedence

Category

Operators

Primary

(x) x.y x->y f(x) a[x] x++ x−− new typeof sizeof checked unchecked stackalloc

Unary

+ - ! ˜ ++x −− x (T)x *x &x

Multiplicative

* / %

Additive

+ -

Shift

<< >>

Relational

< > <= >= is as

Equality

== !=

Logical AND

&

Logical XOR

^

Logical OR

|

Conditional AND

&&

Conditional OR

||

Conditional

?:

Assignment

= *= /= %= += -= <<= >>= &= ^= |=

In some complex equations, you might need to nest your parentheses to ensure the proper order of operations. Let’s assume we want to know how many seconds a fictional family wastes each morning. It turns out that the adults spend 20 minutes over coffee each morning and 10 minutes reading the newspaper. The children waste 30 minutes dawdling and 10 minutes arguing.

Here’s our algorithm:

(((minDrinkingCoffee + minReadingNewspaper )* numAdults ) +
((minDawdling + minArguing) * numChildren)) * secondsPerMinute)

Although this works, it is hard to read and get right. It’s much easier to use interim variables:

wastedByEachAdult = minDrinkingCoffee + minReadingNewspaper;
wastedByAllAdults = wastedByEachAdult * numAdults;
wastedByEachKid = minDawdling + minArguing;
wastedByAllKids = wastedByEachKid * numChildren;
wastedByFamily = wastedByAllAdults + wastedByAllKids;
totalSeconds = wastedByFamily * 60;

The latter example uses many more interim variables, but it is far easier to read, to understand, and (most important) to debug. As you step through this program in your debugger, you can see the interim values and make sure they are correct.

The Ternary Operator

Although most operators require one term (e.g., myValue++) or two terms (e.g., a+b), there is one operator that has three: the ternary operator (?:):

conditional-expression ? expression1 : expression2

This operator evaluates a conditional expression (an expression that returns a value of type bool), and then returns the value of either expression1 if the value returned from the conditional expression is true, or expression2 if the value returned is false. The logic is “if this is true, return the first; otherwise, return the second.” Example 3-18 illustrates.

Example 3-18. The ternary operator
using System;
using System.Collections.Generic;
using System.Text;


namespace TernaryOperator
{
  class TernaryOperator
  {
    static void Main( string[] args )
    {
      int valueOne = 10;
      int valueTwo = 20;


      int maxValue = valueOne > valueTwo ? valueOne : valueTwo;


      Console.WriteLine( "ValueOne: {0}, valueTwo: {1}, maxValue: {2}",
        valueOne, valueTwo, maxValue );


    }
  }
}


Output:
ValueOne: 10, valueTwo: 20, maxValue: 20

In Example 3-18, the ternary operator is being used to test whether valueOne is greater than valueTwo. If so, the value of valueOne is assigned to the integer variable maxValue; otherwise, the value of valueTwo is assigned to maxValue.

Preprocessor Directives

In the examples you’ve seen so far, you’ve compiled your entire program whenever you compiled any of it. At times, however, you might want to compile only parts of your program—for example, depending on whether you are debugging or building your production code.

Before your code is compiled, another program called the preprocessor runs and prepares your program for the compiler. The preprocessor examines your code for special preprocessor directives, all of which begin with the pound sign (#). These directives allow you to define identifiers and then test for their existence.

Defining Identifiers

#define DEBUG defines a preprocessor identifier, DEBUG. Although other preprocessor directives can come anywhere in your code, identifiers must be defined before any other code, including using statements.

Tip

C and C++ programmers take note: the C# preprocessor implements only a subset of the C++ preprocessor and doesn’t support macros.

You can test whether DEBUG has been defined with the #if statement. Thus, you can write:

#define DEBUG


//... some normal code - not affected by preprocessor


#if DEBUG
  // code to include if debugging
#else
  // code to include if not debugging
#endif


//... some normal code - not affected by preprocessor

When the preprocessor runs, it sees the #define statement and records the identifier DEBUG. The preprocessor skips over your normal C# code, and then finds the #if - #else - #endif block.

The #if statement tests for the identifier DEBUG, which does exist, and so the code between #if and #else is compiled into your program—but the code between #else and #endif is not compiled. That code doesn’t appear in your assembly at all; it is as though it were left out of your source code.

Had the #if statement failed—that is, if you had tested for an identifier that did not exist—the code between #if and #else would not be compiled, but the code between #else and #endif would be compiled.

Tip

Any code not surrounded by #if/#endif is not affected by the preprocessor and is compiled into your program.

Undefining Identifiers

You undefine an identifier with #undef. The preprocessor works its way through the code from top to bottom, so the identifier is defined from the #define statement until the #undef statement, or until the program ends. Thus, if you write:

#define DEBUG


#if DEBUG
  // this code will be compiled
#endif


#undef DEBUG


#if DEBUG
  // this code will not be compiled
#endif

the first #if will succeed (DEBUG is defined), but the second will fail (DEBUG has been undefined).

#if, #elif, #else, and #endif

There is no switch statement for the preprocessor, but the #elif and #else directives provide great flexibility. The #elif directive allows the else-if logic of “if DEBUG then action one, else if TEST then action two, else action three”:

#if DEBUG
  // compile this code if debug is defined
#elif TEST
  // compile this code if debug is not defined
  // but TEST is defined
#else
  // compile this code if neither DEBUG nor TEST
  // is defined
#endif

In this example, the preprocessor first tests to see whether the identifier DEBUG is defined. If it is, the code between #if and #elif will be compiled, and the rest of the code until #endif will not be compiled.

If (and only if) DEBUG is not defined, the preprocessor next checks to see whether TEST is defined. Note that the preprocessor will not check for TEST unless DEBUG is not defined. If TEST is defined, the code between the #elif and #else directives will be compiled. If it turns out that neither DEBUG nor TEST is defined, the code between the #else and the #endif statements will be compiled.

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

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