Freigeben über


C# Language Fundamentals

This chapter is excerpted from Learning C# 3.0: Master the fundamentals of C# 3.0 by Jesse Liberty, Brian MacDonald, published by O'Reilly Media

Learning C# 3.0

Logo

Buy Now

Chapter 1, C# and .NET Programming demonstrates a very simple C# program that prints the text string "Hello World!" to the console screen and provides a line-by-line analysis of that program. However, even that simple program was complex enough that we had to skip some of the details. In this chapter, we'll begin an in-depth exploration of the syntax and structure of the C# language. The syntax of a language is the order of the keywords, where you put semicolons, and so forth. The semantics are what you are expressing in the code, and how your code fits together. Syntax is trivial and unimportant, but because compilers are absolute sticklers for correct syntax, novice programmers pay a lot of attention to syntax until they are comfortable. Fortunately, Visual Studio 2008 makes managing syntax much easier so that you can focus on semantics, which are far more important.

In this chapter, we'll introduce statements and expressions, the building blocks of any program. You'll learn about variables and constants, which let you store values for use in your program. We'll also begin an explanation of types, and we'll take a look at strings, which you saw briefly in the Hello World program. This is all very basic stuff, but it's the foundation you need to start getting fancy. Without variables, your applications can't actually process any data. All variables need types, and variables are used in expressions. You'll see how neatly this all fits together.

Statements

In C#, a complete program instruction is called a statement and each statement ends with a semicolon (;). Forgetting a semicolon is a very common mistake for novice programmers, but Visual Studio will catch you if you do it. Programs consist of sequences of statements, such as the following:

int myVariable;                   // a statement
myVariable = 23;                  // another statement
int anotherVariable = myVariable; // yet another statement

The compiler starts at the beginning of a source code file and reads downward, executing each statement in the order in which the compiler encounters it. This would be entirely straightforward, and terribly limiting, were it not for branching. Branching allows you to change the order in which statements are evaluated, and even take different paths depending on the value of your variables, but let's not get ahead of ourselves. We'll get to branching in Chapter 5, Branching.

Types

C# is a strongly typed language. That means that every object you create or use in a C# program must have a specific type. In other words, you must declare the object to be an integer or a string or a Dog or a Button. Essentially, the type indicates the characteristics of the object and what it can do.

Types come in two flavors: those that are built into the language (intrinsic types) and those you create yourself (classes and interfaces, discussed in Chapters Chapter 7, Classes and Objects and Chapter 13, Interfaces). C# offers a number of intrinsic types, shown in Table 3.1, "The intrinsic types built into C#".

Table 3.1. The intrinsic types built into C#

C# type

Size (in bytes)

.NET type

Description

byte

1

Byte

Unsigned (values between 0 and 255).

char

2

Char

Unicode characters (a modern way of storing most characters, including international language characters).

bool

1

Boolean

True or false.

sbyte

1

SByte

Signed (values between -128 and 127).

short

2

Int16

Signed (short) (values between -32,768 and 32,767).

ushort

2

UInt16

Unsigned (short) (values between 0 and 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 the values from approximately +/-1.5 x 10-45 to approximately +/-3.4 x 1038 with seven significant figures.

double

8

Double

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

decimal

12

Decimal

Fixed-precision up to 28 digits and the position of the decimal point. This type is typically used in financial calculations. Requires the suffix "m" or "M" when you declare a constant.

long

8

Int64

Signed integers ranging 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 approximately 1.85 x 1019.

Each type has a name (such as int) and a size (such as 4 bytes). The size tells you how many bytes each object of this type occupies in memory. Programmers generally don't like to waste memory if they can avoid it, but with the cost of memory these days, you don't need to be particular about the memory cost of types. Most of the time, if you're using whole numbers you'll use an int, even if a short would be fine. Likewise, double is the commonly used type for decimal numbers, even though float is just fine in most cases. The Description column in Table 3.1, "The intrinsic types built into C#" lists the minimum and maximum values you can hold in objects of each type.

Tip

Each C# type corresponds to an underlying .NET type. Thus, what C# calls an int, .NET calls an Int32. This is interesting only if you care about sharing objects across languages.

Intrinsic types aren't very flexible, although you'll use them a lot. You can use them to add two numbers together, and they can display their values as strings. User-defined types can do a lot more; their abilities are determined by the methods you create, which we'll get to in Chapter 8, Inside Methods.

Objects of an intrinsic type are called variables, and we'll talk about those later in this chapter.

Numeric Types

Most of the intrinsic types are used for working with numeric values (byte, sbyte, short, ushort, int, uint, float, double, decimal, long, and ulong).

You can divide the numeric types into two sets: unsigned and signed. An unsigned value (byte, ushort, uint, ulong) can hold only positive values. A signed value (sbyte, short, int, long) can hold positive or negative values, but in a different range of values. For example, short and ushort are both 16-bit values, which means they can hold one of 65,536 possible values (216). The ushort holds only positive (unsigned) numbers, so the range is from 0 to 65,535 (not 65,536, because you need one spot for zero). A short is signed, and can hold values from -32,768 to 32,767 (again, not 32,768, because you need one spot for zero).

You also can categorize types into those used for integer values (whole numbers) and those used for floating-point values (fractional or rational numbers). The byte, sbyte, ushort, uint, ulong, short, int, and long types all hold whole number values.

Tip

The byte and sbyte types are not used very often, and we won't describe them in this book.

The double and float types hold fractional values. Although double is larger than float, and you would think that float would suffice most of the time, the compiler assumes that any number with a decimal point in it is a double unless you follow the number with the letter f. That is, 4.7 is assumed to be double, but 4.7f is a float. The decimal value type was added to the language to support scientific and financial applications; to use it, you append an m to the end, just as you do with the f for a float. The float and double types have a very slight amount of imprecision to them if the values are very large or very small-not something you'll need to worry about in this book, certainly, but it might cause problems if you were trying to do very precise scientific or financial calculations, which is why decimal is there.

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 through 65,535, whereas a uint can hold values from 0 through 4,294,967,295.

That being said, in real life most of the time you'll simply declare your numeric variables to be of type int, unless there is a good reason to do otherwise. (Most programmers choose signed types unless they have a good reason to use an unsigned value. This is, in part, just a matter of tradition.)

Suppose you need to keep track of inventory for a book warehouse. You expect to house up to 40,000 or even 50,000 copies of each book. A signed short can only hold up to 32,767 values. You might be tempted to use an unsigned short (which can hold up to 65,535 values), but it is easier and preferable to just use a signed int (with a maximum value of 2,147,483,647). That way, if you have a runaway bestseller, your program won't break (if you anticipate selling more than 2 billion copies of your book, perhaps you'll want to use a long!).

Tip

Throughout this book, we will use int wherever it works, even if short or byte might be usable alternatives. Memory is cheap these days, and programmer time is expensive. There are circumstances where the difference in memory usage would be significant (for example, if you are going to hold 1 billion values in memory), but we'll keep things simple by using the int type whenever possible.

Nonnumeric Types: char and bool

In addition to the numeric types, the C# language offers two other types: char and bool.

The char type is used from time to time when you need to hold a single character. The char type can represent a simple character (A), a Unicode character (\u0041), or an escape sequence ('\n'). We won't discuss Unicode characters in this book, and you'll see escape sequences later, where we'll explain them in context. When you refer to a char in your code, you need to surround it with single quotes, like this: 'A'.

The one remaining important type is bool, which holds a Boolean value. A Boolean value is one that is either true or false. Boolean values are used frequently in C# programming, as you'll see throughout this book. Virtually every comparison (is myDog bigger than yourDog?) results in a Boolean value.

Tip

The bool type was named after George Boole (1815-1864), an English mathematician who published An Investigation into the Laws of Thought, on Which Are Founded the Mathematical Theories of Logic and Probabilities, and thus created the science of Boolean algebra.

Types and Compiler Errors

The compiler will help you by complaining if you try to use a type improperly. The compiler complains in one of two ways: it issues a warning or it issues an error.

Tip

You are well advised to treat warnings as errors. Stop what you are doing, figure out why there is a warning, and fix the problem. Never ignore a compiler warning unless you are certain that you know exactly why the warning was issued and that you know something the compiler does not. To have Visual Studio enforce this for you, follow these steps:

  1. Right-click on a project in the Solution Explorer, and select Properties from the pop-up menu.

  2. Click on the Build tab in the Properties window.

  3. In the "Treat all warnings as errors" section of the page, select the All radio button.

Programmers talk about design time, compile time, and runtime. Design time is when you are designing the program, compile time is when you compile the program, and runtime is (surprise!) when you run the program.

The earlier in your development process that you unearth a bug, the better. It is easier to fix a bug in your logic at design time than to fix the bug once it has been written into code. Likewise, it is better (and cheaper) to find bugs in your program at compile time than at runtime. Not only is it better, it is more reliable. A compile-time bug will fail every time you run the compiler, but a runtime bug can hide. Runtime bugs can slip under a crack in your logic and lurk there (sometimes for months), biding their time, waiting to come out when it will be most expensive (or most embarrassing) to you.

It will be a constant theme of this book that you want the compiler to find bugs. The compiler is your friend (though we admit, at times it feels like your nemesis). The more bugs the compiler finds, the fewer bugs your users will find.

A strongly typed language such as C# helps the compiler find bugs in your code. Here's how: suppose you tell the compiler that milo is of type Dog. Sometime later you try to use milo to display text (calling the ShowText method). Oops, Dogs don't display text. Your compiler will stop with an error:

Dog does not contain a definition for 'ShowText'

Very nice. Now you can go figure out whether you used the wrong object or you called the wrong method.

Visual Studio .NET actually finds the error even before the compiler does. When you try to add a method, as soon as you type the dot character, IntelliSense pops up a list of valid methods to help you, as shown in Figure 3.1, "IntelliSense is your friend. When you start to type in a method, IntelliSense helpfully provides a list of possible methods, to ensure that you pick a valid one.".

Figure 3.1. IntelliSense is your friend. When you start to type in a method, IntelliSense helpfully provides a list of possible methods, to ensure that you pick a valid one.

IntelliSense is your friend. When you start to type in a method, IntelliSense helpfully provides a list of possible methods, to ensure that you pick a valid one.

When you try to add a method that does not exist, it won't be in the list. That is a pretty good clue that you are not using the object properly.

WriteLine( ) and Output

The .NET Framework provides a useful method for displaying output on the screen in console applications: System.Console.WriteLine( ). How you use this method will become clearer as you progress through the book, but the fundamentals are straightforward. You call the method, and in the parentheses you pass in a string that you want printed to the console (the screen), as in the Hello World application in Chapter 1, C# and .NET Programming.

That's useful; a string is fixed text, and you might want to output a value that can change, depending on the content of your program. For that, you can also pass in substitution parameters. A substitution parameter is just a placeholder for a value you want to display. For example, you might pass in the substitution parameter {0} as part of the string, and then when you run the program, you'll substitute the value held in the variable myInt so that its value is displayed where the parameter {0} appears in the WriteLine( ) statement.

Here's how it works. Each substitution parameter is a number between braces, starting with 0 for the first parameter, 1 for the next, and so on. So, if you're using just one substitution parameter, it might look like this:

System.Console.WriteLine("Age of student: {0}", myInt);

Notice that you follow the quoted string with a comma and then a variable name. The value of the variable will be substituted into the parameter. If myInt has the value 15, the statement shown previously causes the following to display:

Age of student: 15

If you have more than one parameter, the variable values will be substituted in the order they appear in the method, as in the following:

System.Console.WriteLine("Age of first student: {0},
   age of second student: {1}", myInt, myOtherInt);

If myInt has the value 15 and myOtherInt has the value 20, this will cause the following to display:

Age of first student: 15, and age of second student: 20.

There are other special characters that you can use to format the output, like this:

System.Console.WriteLine("Student ages:\nFirst student:\t{0}\n
                         Second student:\t{1}", myInt, myOtherInt);

This produces output that looks like this:

Student ages:
First student:    15
Second student:   20

The characters that begin with a slash character (\) are called escaped characters. The slash is a signal to the compiler that what follows is an escaped character. The code and the slash together are considered a single character. The escaped character \t indicates a tab, and the character \n indicates a newline (a line feed, or a carriage return). The string here will print the characters Studentages: followed by a newline (\n), then the text Firststudent: followed by a tab (\t), then the value of the first parameter ({0}), and a newline character (\n), then the text Secondstudent: followed by a tab (\t), and finally the value of the second parameter ({1}).

You'll see a great deal more about WriteLine( ) in later chapters.

Variables and Assignment

A C# variable is roughly the same as the variables you remember from your ninth grade algebra class: it's a placeholder for a value. To put it more technically, a variable is an instance of an intrinsic type (such as int) that can hold a value:

int myVariable = 15;

You initialize a variable by writing its type (int in this case), its identifier, and then assigning a value to that variable. The equals sign (=) is the operator for assignment. You're not defining an equation in a mathematical sense; you're telling the compiler to set the contents of the variable on the left of the operator to the value of whatever is on the right of the operator. In this specific case, you're saying "myVariable is an int, and it's assigned the value of 15." There are other operators, and we'll cover them in Chapter 4, Operators, but you need to know about assignment now because variables aren't much good without it.

An identifier is just an arbitrary name you assign to a variable, method, class, or other element. In this case, the variable's identifier is myVariable.

You can define variables without initializing them; just leave off the assignment and the value:

int myVariable;

You can then assign a value to myVariable later in your program:

int myVariable;
// some other code here
myVariable = 15; // assign 15 to myVariable

You can also change the value of a variable later in the program. That is why they're called variables; their values can vary, and that's what makes them useful.

int myVariable;
// some other code here
myVariable = 15; // assign 15 to myVariable
// some other code here
myVariable = 12; // now it is 12

Technically, a variable is a named storage location (that is, stored in memory) with a type. After the final line of code in the previous example, the value 12 is stored in the named location myVariable.

Example 3.1, "You initialize a variable by declaring its type and assigning it a value; later, you can assign it a different value" illustrates the use of variables. To test this program, open Visual Studio .NET and create a console application, just as you did with Hello World in Chapter 1, C# and .NET Programming. Type in the code shown in bold in Example 3.1, "You initialize a variable by declaring its type and assigning it a value; later, you can assign it a different value".

Example 3.1. You initialize a variable by declaring its type and assigning it a value; later, you can assign it a different value

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

namespace Example_3_1_ _ _ _Using_variables
{
    class Values
    {
        static void Main( )
        {
            int myInt = 7;
            System.Console.WriteLine("Initialized, myInt: {0}", myInt);
            myInt = 5;
            System.Console.WriteLine("After assignment, myInt: {0}", myInt);
        }
    }
}

Press Ctrl-F5, or select Debug → Start Without Debugging to build and run this application. As we mentioned in Chapter 1, C# and .NET Programming, if you press F5, the console window will disappear almost immediately; using Ctrl-F5 allows the window to stick around so that you can read it. The output looks like this:

Initialized, myInt: 7
After assignment, myInt: 5

Example 3.1, "You initialize a variable by declaring its type and assigning it a value; later, you can assign it a different value" initializes the variable myInt to the value 7, displays that value, reassigns the variable with the value 5, and displays it again.

Definite Assignment

C# requires definite assignment; you have to initialize a variable, or assign a value to it, before you can "use" it-that is, before you can output it or manipulate it in any way. To test this rule, change the line that initializes myInt in Example 3.1, "You initialize a variable by declaring its type and assigning it a value; later, you can assign it a different value" to:

int myInt;

Save the revised program shown in Example 3.2, "You have to initialize variables before you can use them; this code won't compile".

Example 3.2. You have to initialize variables before you can use them; this code won't compile

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

namespace Example_3_2_ _ _ _Definite_Assignment
{
    class Values
    {
        static void Main( )
        {
            int myInt;
            System.Console.WriteLine("Initialized, myInt: {0}", myInt);
            myInt = 5;
            System.Console.WriteLine("After assignment, myInt: {0}", myInt);
        }
    }
}

When you try to compile Example 3.2, "You have to initialize variables before you can use them; this code won't compile", the C# compiler will open the Error List window in the IDE, and will display the following error message:

Use of unassigned local variable 'myInt'

You can't use an uninitialized variable in C#; doing so violates the rule of definite assignment. In this case, "using" the variable myInt means passing it to WriteLine( ).

So, does this mean you must initialize every variable? No, but if you don't initialize your variable, you must assign a value to it before you attempt to use it. Example 3.3, "This code fixes Example 3.2, "You have to initialize variables before you can use them; this code won't compile"; you don't have to initialize the variable when you create it, but you do have to assign some value to it before you use it" illustrates a corrected program.

Example 3.3. This code fixes Example 3.2, "You have to initialize variables before you can use them; this code won't compile"; you don't have to initialize the variable when you create it, but you do have to assign some value to it before you use it

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

namespace Example_3_3_ _ _ _Definite_assignment
{
    class Values
    {
        static void Main( )
        {
            int myInt;
            //other code here...
            myInt = 7; // assign to it
            System.Console.WriteLine("Assigned, myInt: {0}", myInt);
            myInt = 5;
            System.Console.WriteLine("Reassigned, myInt: {0}", myInt);
        }
    }
}

You can even assign the same value to multiple variables, like this:

int a, b, c, d;
a = b = c = d = 5;

In this case, a, b, c, and d would all have the value 5. This works because the C# compiler performs the rightmost assignment first; that is, d=5. That assignment itself returns a value, the value 5. The compiler then assigns that returned value to c. That second assignment also returns a value, and so on, until all the variables have been assigned.

Implicitly Typed Variables

There's one additional type of variable we can discuss, now that you understand assignment: the implicitly typed variable. The C# compiler can determine the type of a variable by analyzing the type of the value that you assign to it. For example, look at these assignment statements:

var firstVariable = 6;
var secondVariable = 3.5;
var thirdVariable = "I'm a string!";

The compiler assigns firstVariable as type int, secondVariable as type double, and thirdVariable as type string. You don't have to explicitly assign the type.

Be very clear, though: these variables are typed, and if you later try to assign an object of the wrong type to them you will generate a compiler error. And once the implicit type is set, it cannot be changed, not even explicitly. If you try to do something like this, you'll get an error:

firstVariable = secondVariable;

Just as if you had explicitly declared firstVariable to be of type int, once it is implicitly typed, you cannot assign the value of a variable of type double to it, because you would lose part of the value, as you'll see in Chapter 4, Operators.

You may be wondering: if the compiler can determine the types of the variables for you, why not just use var all the time, instead of bothering with explicitly declaring types? A key reason is that implicit types make your code harder to read, and thus harder to maintain. This is more than enough reason to avoid using var except where needed. A second reason is that the compiler may guess incorrectly, and thus introduce small but nasty errors into your code. If you were writing a math program, and you used var with your assignments like this:

var a = 12;
var b = 7;

the compiler will decide that both a and b should be of type int. But if you were thinking they should be of type double, and later try something like this:

a = 7.4;
b = 5.5;

you'll get an error, because the compiler can't read your mind.

Casting

You can cause the compiler to ignore its rules of type safety and treat an object of one type as though it were an object of another type, if the types are compatible. This is called casting. Casting can be either implicit or explicit.

An implicit conversion happens automatically; the compiler takes care of it for you. If you have a short, and you assign it to a variable of type int, the compiler automatically (and silently) casts it for you. You don't have to take any action. This is safe, because an int variable can hold any value that might have been in a short variable.

short myShort = 5;
// other code here...
int myint = myShort; // implicit conversion

Programmers often talk about this as though the short were being turned into an int. What is actually happening is that the compiler is accepting a short where it expects to find an int, because that is a safe thing to do.

Implicit conversion works only when there is no possibility of loss of data, though. Take a look at this:

int myInt;
double myDouble = 4.7;
myInt = myDouble;

If you try that assignment, the compiler gives you an error message because you can't squeeze a decimal number into an integer space. You would lose the fractional part (.7). In fact, even if you wrote:

int myInt;
double myDouble = 4
myInt = myDouble;

the compiler would still generate an error. Although there is no fractional part to lose in this case (the double is holding a whole number), the compiler can't take the chance that something might happen that could change the conditions, so it simply rejects all assignments of a double to an int.

This is where explicit conversions come in-when there is danger of losing data. For example, although the compiler will let you convert an int to a double implicitly (there's no chance you can lose data), it will not let you implicitly convert a double to an int, as you've seen.

If you happen to know that it is perfectly safe in your particular situation to make the assignment-if you know what you are doing-you can force the compiler to accept the assignment with an explicit cast.

Again, some programmers talk about "casting the double into an integer," but you're not really changing either variable at all; you're just instructing the compiler to ignore its type-safety rules for this moment and accept the assignment of the value held by the double variable to the integer variable. The compiler will comply, and it will in fact throw away any fractional part.

You specify an explicit conversion by placing the type you want to assign to in parentheses, immediately in front of the variable with the risky value:

int myDouble = 4.7;
// other code here...
int myInt = (int) myDouble; // explicit conversion

The keyword (int) is necessary to make the explicit conversion; without it the compiler will generate an error. Notice, though, that in this case, you're slicing off the fractional value of the double; and myInt will have only the integral part (4), which is why we say you should know what you're doing.

Sometimes you need an explicit conversion (though not often), but it is usually when you are taking an object out of a collection and you need to convert it to its "real" type, all of which we'll discuss in a later chapter. Even then, you test to make sure the object you have is what you think it is. With this sort of explicit conversion, you are almost guaranteed to lose value sooner or later.

Constants

Variables are a powerful tool, but sometimes you want to use a defined value, one whose value you want to ensure remains constant. A constant is like a variable in that it can store a value. However, unlike a variable, you cannot change the value of a constant while the program runs.

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 these values FreezingPoint and BoilingPoint, but you do not want to permit their values to be changed while the program is executing. The solution is to use a constant. Constants come in three flavors: literals, symbolic constants, and enumerations.

Literal Constants

A literal constant is just a value. For example, 32 is a literal constant. It does not have a name; it is just a literal value. And you can't make the value 32 represent any other value. The value of 32 is always 32. You can't assign a new value to 32, and you can't make 32 represent the value 99 no matter how hard you might try. You'll use literal constants a lot, but you probably won't think of them as such.

Symbolic Constants

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

const type identifier =value;

The const keyword is followed by a type, an identifier, the assignment operator (=), and the value to assign to the constant.

This is similar to declaring a variable except that you start with the keyword const and symbolic constants must be initialized. Once initialized, a symbolic constant cannot be altered. For example, in the following declaration, 32 is a literal constant and FreezingPoint is a symbolic constant of type int:

const int FreezingPoint = 32;

Example 3.4, "This program defines two symbolic constants and outputs their values; it won't compile, though, because of the attempt to assign a new value to a constant" illustrates the use of symbolic constants.

Example 3.4. This program defines two symbolic constants and outputs their values; it won't compile, though, because of the attempt to assign a new value to a constant

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

namespace Example_3_4_ _ _ _Symbolic_Constants
{
    class Values
    {
        static void Main( )
        {
            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 = 21;
        }
    }
}

Example 3.4, "This program defines two symbolic constants and outputs their values; it won't compile, though, because of the attempt to assign a new value to a constant" creates two symbolic integer constants: FreezingPoint and BoilingPoint. (See the "Naming Conventions" sidebar for a discussion of how to name symbolic constants.)

These constants serve the same purpose as using the literal values 32 and 212 for the freezing and boiling points of water, respectively, in expressions that require them. However, because the constants have names, they convey far more meaning. It might seem easier to just use the literal values 32 and 212 instead of going to the trouble of declaring the constants, but if you decide to switch this program to Celsius, you can reinitialize these constants at compile time to 0 and 100, respectively, and all the rest of the code should continue to work.

If you try to run the program shown in Example 3.4, "This program defines two symbolic constants and outputs their values; it won't compile, though, because of the attempt to assign a new value to a constant", you'll receive the following error:

The left-hand side of an assignment must be a variable, property or indexer

That's because the assignment in this line is illegal:

BoilingPoint = 21;

You can't assign a new value to a constant, so the compiler complains. To fix this problem, simply comment out the offending line by adding two slashes in front of it, like this:

// BoilingPoint = 21;

Now the program runs as expected, without an error.

Naming Conventions

Microsoft has issued instructions on how you should name the variables, constants, and other objects in your program. They define two types of naming conventions: Camel notation and Pascal notation.

In Camel notation, names begin with a lowercase letter. Multiword names (such as "my button") are written with no spaces and no underscore and with each word after the first capitalized. Thus, the correct name for "my button" is myButton.

Pascal notation is just like Camel notation except that the first letter is also uppercase (FreezingPoint).

Microsoft suggests that variables be written with Camel notation and constants with Pascal notation. In later chapters, you'll learn that member variables are named using Camel notation, whereas methods and classes are named using Pascal notation.

Enumerations

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

In Example 3.4, "This program defines two symbolic constants and outputs their values; it won't compile, though, because of the attempt to assign a new value to a constant", you created two related constants:

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

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

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

Notice, however, that this process is somewhat cumbersome; also, this syntax doesn't show any logical connection among these various constants-they're just a set of unrelated values. You know that these constants all refer to temperature, but the compiler has no way of knowing that. C# provides an alternative construct, the enumeration, which allows you to group logically related constants, as in the following:

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

Tip

The entries in the enumeration are separated by commas. Many programmers like to leave a comma after the last entry in an enumeration as a convenience for adding more values later. Other programmers find this, at best, sloppy. The code will compile either way.

The syntax for specifying an enumeration uses the enum keyword, as follows:

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

Tip

In a specification statement such as the preceding example, the square brackets indicate an optional element. Thus, you can declare an enum with no base type, simply by leaving it out (leave out the square brackets as well).

There are also optional attributes and modifiers you can use, but you don't need them right now. An enumeration begins with the keyword enum, which is generally followed by an identifier; in this case, Temperatures:

enum Temperatures

The base type is the underlying type for the enumeration. You might specify that you are declaring constant ints, constant longs, or something else. 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 (ushort, long) except for char. For example, the following fragment declares an enumeration with unsigned integers (uint) as the base type:

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

Notice that an enum declaration ends with the enumerator list, which contains the constant assignments for the enumeration, each separated by a comma. Example 3.5, "An enumeration represents a set of values that you don't want to change while your program is running" rewrites Example 3.4, "This program defines two symbolic constants and outputs their values; it won't compile, though, because of the attempt to assign a new value to a constant" to use an enumeration.

Example 3.5. An enumeration represents a set of values that you don't want to change while your program is running

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

namespace Example_3_5_ _ _ _Enumerations
{
    class Values
    {
        // declare the enumeration
        enum Temperatures
        {
            WickedCold = 0,
            FreezingPoint = 32,
            LightJacketWeather = 60,
            SwimmingWeather = 72,
            BoilingPoint = 212,
        }

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

In Example 3.5, "An enumeration represents a set of values that you don't want to change while your program is running", you declare an enumerated constant called Temperatures. When you want to use any of the values in an enumeration in a program, the values of the enumeration must be qualified by the enumeration name. That is, you can't just refer to FreezingPoint; instead, you use the enumeration identifier (Temperature) followed by the dot operator and then the enumerated constant (FreezingPoint). This is called qualifying the identifier FreezingPoint. Thus, to refer to the FreezingPoint, you use the full identifier Temperature.FreezingPoint.

You might want to display the value of an enumerated constant to the console, as in the following:

Console.WriteLine("The freezing point of water is {0}",
                  (int) Temperature.FreezingPoint);

To make this work properly, you must cast the constant to its underlying type (int). In this case, you are saying, "Treat this enumerated constant as an int." Because you know the underlying type is int, this is safe to do. (See "Casting" earlier in this chapter.)

In Example 3.5, "An enumeration represents a set of values that you don't want to change while your program is running", the values in the two enumerated constants FreezingPoint and BoilingPoint are both cast to type int; then that integer value is passed to WriteLine( ) and displayed.

Each constant in an enumeration corresponds to a numerical value. In Example 3.5, "An enumeration represents a set of values that you don't want to change while your program is running", each enumerated value is an integer. If you don't specifically set it otherwise, the enumeration begins at 0 and each subsequent value counts up from the previous. Thus, 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.

Strings

It is nearly impossible to write a C# program without creating strings, and we wouldn't want to deprive you of them here. Strings are actually complex classes that we'll cover much more thoroughly in Chapter 15, Strings. For now, though, all you need to know is that 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 type:

string myString;

You specify a string literal by enclosing it in double quotes:

"Hello World"

You already used a string literal back in Chapter 1, C# and .NET Programming, in the Hello World example.

You'll frequently initialize a string variable by assigning it a string literal:

string myString = "Hello World";

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. Thus, you can write:

myVariable = 5;

or:

myVariable            =               5      ;

and the compiler will treat the two statements as identical. The key is to use whitespace to make the program more readable to the programmer; the compiler is indifferent.

The exception to this rule is that whitespace within a string is treated as literal; it is not ignored. If you write:

Console.WriteLine("Hello World")

each space between "Hello" and "World" is treated as another character in the string. (In this case, there is only one space character.)

Problems arise only when you do not leave space between logical program elements that require it. For instance, the expression:

int                  myVariable     =                5          ;

is the same as:

int myVariable=5;

but it is not the same as:

intmyVariable =5;

The compiler knows that the whitespace on either side of the assignment operator is extra, but at least some whitespace between the type declaration int and the variable name myVariable is not extra; it is required.

This is not surprising; the whitespace allows the compiler to parse the keyword int rather than some unknown term intmyVariable. You are free to add as much or as little whitespace between int and myVariable as you care to, but there must be at least one whitespace character (typically a space or tab).

Tip

Visual Basic programmers take note: in C#, the end-of-line has no special significance. Statements are ended with semicolons, not newline characters. There is no line continuation character because none is needed.

Summary

  • A complete program instruction is called a statement. Each statement ends with a semicolon (;).

  • All objects, variables, and constants must have a specific type.

  • Most of the intrinsic types are used for working with numeric values. You will commonly use int for whole numbers and double or float for fractional values.

  • The char type is used for holding a single character.

  • The bool type can hold only the value true or false.

  • A variable is an instance of a type. You initialize a variable by creating it with an assigned value.

  • You can use the var keyword to create a variable without a type, but only if you assign it immediately. The complier will determine the type of the variable from the value assigned.

  • You can cast a value from one type to another as long as the compiler knows how to turn the original type into the cast-to type. If no information can be lost, you may cast from one type to another implicitly. If information may be lost, you must cast explicitly. You accomplish the cast by prefacing the variable with the name of the type you want to cast to, in parentheses.

  • A constant is similar to a variable, but the value cannot be changed while the program is running. Literal constants are simply values used on their own. Symbolic constants, indicated with the const keyword, are values with assigned names, which you use like variables, but the values cannot change.

  • An enumeration is a value type that consists of a set of named constants.

  • A string object holds a series of characters (such as a word or sentence). A string literal is simply text enclosed by double quotes. You can assign a string to a string variable, just as you would make any other assignment.

  • Extra whitespace (spaces, tabs, and newline characters) is ignored by the compiler, unless it appears within a string.

We promised you fundamentals in this chapter, and that's what you got. Just about every programming language you want to learn starts with data types, variables, and assignment. Without variables to hold your data, there isn't much to program with. So, now you know how to hold onto data within the bounds of your program. But what can you do with it? At the moment, you know how to print it out to the screen, and that's about it. That will change in Chapter 4, Operators. There, we'll show you how to manipulate the data with some basic operators, just like you remember from math class. We'll also show you how to compare variables too, which may not sound like much, but it's critically important, as you'll see in Chapter 5, Branching.

Test Your Knowledge: Quiz

Question 3-1. What defines a statement in C#?

Question 3-2. What values can a bool type have?

Question 3-3. What are the two kinds of types in C#, and what's the difference between them?

Question 3-4. What is the difference between a float and a double?

Question 3-5. What's the definition of a variable?

Question 3-6. What does definite assignment mean?

Question 3-7. Which of the following code statements will compile?

int myInt = 25;
long myLong = myInt;
int newInt = myLong;

Question 3-8. For each of the following pieces of data, which variable type would you use, and which should be represented with constants?

  • Your age in years

  • The speed of light in meters per second

  • The number of widgets in your warehouse

  • The amount of money in your bank account

  • The text of the U.S. Declaration of Independence

Question 3-9. Given the following declaration, how would you refer to the constant for Green and what would its value be?

enum WavelengthsOfLight
{
  Red = 7000,
  Orange = 6200,
  Yellow = 5800,
  Green = 5300,
  Blue = 4700,
  Violet = 4200
}

Question 3-10. How do you indicate a string literal?

Test Your Knowledge: Exercises

Exercise 3-1. We'll start easy for this project. Write a short program that creates five variables, one of each of the following types: int, float, double, char, and string. Name the variables whatever you like. Initialize the variables with the following values:

  • int: 42

  • float: 98.6

  • double: 12345.6789

  • char: Z

  • string: The quick brown fox jumped over the lazy dogs.

Then, output the values to the console.

Exercise 3-2. As you gain more experience with programming, you'll frequently find yourself adapting some code that you wrote before, instead of writing a new program from scratch-and there's no time like the present to start. Modify the program in Exercise 3-1 so that after you've output the values of the variables the first time, you change them to the following:

  • int: 25

  • float: 100.3

  • double: 98765.4321

  • char: M

  • string: A quick movement of the enemy will jeopardize six gun boats.

Then output the values to the console a second time.

Exercise 3-3. Write a new program to declare a constant double. Call the constant Pi, set its value to 3.14159, and output its value to the screen. Then change the value of Pi to 3.1 and output its value again. What happens when you try to compile this program?

Exercise 3-4. Write a new program and create a constant enumeration with constants for each month of the year. Give each month the value equal to its numeric place in the calendar, so January is 1, February is 2, and so on. Then output the value for June, with an appropriate message.