Chapter 2 demonstrates a very simple C# program. Nonetheless, there is sufficient complexity in creating even that little program that some of the pertinent details had to be skipped over. The current chapter illuminates these details by delving more deeply into the syntax and structure of the C# language itself.
This chapter discusses the type system in C#, drawing a distinction
between built-in types (int
,
bool
, etc.) versus user-defined types (types you
create as classes and interfaces). The chapter also covers
programming fundamentals such as how to create and use variables and
constants. It then goes on to introduce enumerations, strings,
identifiers, expressions, and statements.
The second part of the chapter explains and demonstrates the use of
branching, using the if
,
switch
, while
,
do...while
, for
, and
foreach
statements. Also discussed are operators,
including the assignment, logical, relational, and mathematical
operators. This is followed by an introduction to namespaces and a
short tutorial on the C# precompiler.
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#.
C# is a
strongly typed language. In a strongly typed language you must
declare the type of each object you create (e.g., integers, floats,
strings, windows, buttons, etc.) and the compiler will help you
prevent bugs by enforcing that only data of the right type is
assigned to those objects. The type of an object signals to the
compiler the size of that object (e.g., int
indicates an object of 4 bytes) and its capabilities (e.g., buttons
can be drawn, pressed, and so forth).
Like C++ and Java, C# divides types into two sets: intrinsic (built-in) types that the language offers and user-defined types that the programmer defines.
C# also divides the set of types into two other categories:
value
types and reference
types.[5] The principal
difference between value and reference types is the manner in which
their values are stored in memory. A value type holds its actual
value in memory allocated on the stack (or it is allocated as part of
a larger reference type object). The address of a reference type
variable sits on the stack, but the actual object is stored on the
heap.
If you have a very large object, putting it on the heap has many advantages. Chapter 4 discusses the various advantages and disadvantages of working with reference types; the current chapter focuses on the intrinsic value types available in C#.
C# also supports C++ style pointer types, but these are rarely used, and only when working with unmanaged code. Unmanaged code is code created outside of the .NET platform, such as COM objects. Working with COM objects is discussed in Chapter 22.
The C# language offers the usual cornucopia of intrinsic (built-in) types one expects in a modern language, each of which maps to an underlying type supported by the .NET Common Language Specification (CLS). Mapping the C# primitive types to the underlying .NET type ensures that objects created in C# can be used interchangeably with objects created in any other language compliant with the .NET CLS, such as VB .NET.
Each type has a specific and unchanging size. Unlike with C++, a C#
int
is always 4 bytes because it maps to an
Int32
in the .NET CLS. Table 3-1 lists the built-in value types offered by C#.
Table 3-1. C# built-in value types
Type |
Size (in bytes) |
.NET Type |
Description |
---|---|---|---|
1 |
|
Unsigned (values 0-255). | |
1 |
|
Unicode characters. | |
1 |
|
| |
1 |
|
Signed (values -128 to 127). | |
2 |
|
Signed (short) (values -32,768 to 32,767). | |
2 |
|
Unsigned (short) (values 0 to 65,535). | |
4 |
|
Signed integer values between -2,147,483,647 and 2,147,483,647. | |
4 |
|
Unsigned integer values between 0 and 4,294,967,295. | |
4 |
|
Floating point number. Holds the values from approximately +/-1.5 * 10-45 to approximate +/-3.4 * 1038 with 7 significant figures. | |
8 |
|
Double-precision floating point; holds the values from approximately +/-5.0 * 10-324 to approximate +/-1.7 * 10308 with 15-16 significant figures. | |
8 |
|
Fixed-precision up to 28 digits and the position of the decimal point. This is typically used in financial calculations. Requires the suffix “m” or “M.” | |
8 |
|
Signed integers ranging from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807. | |
8 |
|
Unsigned integers ranging from 0 to 0xffffffffffffffff. |
C and C++ programmers take note:
Boolean
variables can only have the values true
or
false
. Integer values do not equate to Boolean
values in C# and there is no implicit conversion.
In addition to these primitive types, C# has two other value types:
enum
(considered later in this chapter) and
struct
(see Chapter 4).
Chapter 4 also discusses other subtleties of value
types such as forcing value types to act as reference types through a
process known as
boxing
,
and that value types do not “inherit.”
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, while a ulong
can hold values from 0 through
4,294,967,295.
That said, memory is fairly cheap, and programmer time is
increasingly expensive; most of the time you’ll simply declare
your variables to be of type int
, unless there is
a good reason to do otherwise.
The signed types are the numeric types of choice of most programmers unless they have a good reason to use an unsigned value.
Although you might be tempted to use an unsigned
short
to double the positive values of a signed
short
(moving the maximum positive value from
32,767 up to 65,535), it is easier and preferable to use a signed
integer (with a maximum value of 2,147,483,647).
It is better to use an unsigned variable when the fact that the value
must be positive is an inherent characteristic of the data. For
example, if you had a variable to hold a person’s age, you
would use an unsigned int
because an age cannot be
negative.
Float
, double
, and
decimal
offer varying degrees of size and
precision. For most small fractional numbers,
float
is fine. Note that the compiler assumes that
any number with a decimal point is a double unless you tell it
otherwise. To assign a literal float
, follow the
number with the letter f
. (Assigning values to
literals is discussed in detail later in this chapter.)
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 while u0041
is a Unicode character.
Escape characters are special two-character tokens in which the first
character is a
backslash.
For example,
is a horizontal tab. The common
escape
characters are shown in Table 3-2.