C# is a strongly typed
language. That means that every object you create or use in a C# program
must have a specific type (e.g., you must declare
the object to be an integer or a string or a Dog or a Button).
Essentially, the type indicates how big the object is (in memory) and
what it can do.
Types come in two flavors: those that are built into the language (intrinsic types ) and those you create (classes and interfaces, discussed in Chapters 7 and 13). C# offers a number of intrinsic types, shown in Table 3-1.
Table 3-1. The intrinsic types
C# type | Size (in bytes) | .NET type | Description |
---|---|---|---|
| 1 | | Unsigned (values 0-255). |
| 2 | | Unicode characters. |
| 1 | | True or false. |
| 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,648 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 approximately +/−3.4 * 1038 with 7 significant figures. |
| 8 | | Double-precision floating point; holds the values from approximately +/−5.0 * 10−324 to approximately +/−1.8 * 10308 with 15–16 significant figures. |
| 12 | | 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 approximately 1.85 * 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 can afford to be mildly
profligate if doing so simplifies your program.) The description field
of Table 3-1 tells you
the minimum and maximum values you can hold in objects of each
type.
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 can’t do much. 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, as discussed in detail in Chapter 8.
Objects of an intrinsic type are called variables . Variables are discussed in detail later in this chapter.
Most of the intrinsic types are used for working with
numeric values (byte
,
sbyte
, short
, ushort
, int
, uint
, float
, double
, decimal
, long
, and ulong
).
The numeric types can be broken 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 the highest value is only half as large as the corresponding
unsigned type. That is, a ushort
can hold any value from 0 through 65,535, but a short
can hold only -32,768 through 32,767.
Notice that 32,767 is nearly half of 65,535 (it is off by one to allow
for holding the value zero). The reason a ushort
can hold up to 65,535 is that 65,535
is a round number in binary arithmetic
(216).
Another way to categorize the types is 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.
The double
and float
types hold fractional values. For most
uses, float
will suffice, unless
you need to hold a really big fractional number, in which case you
might use a double
. The decimal
value type was added to the language
to support scientific and financial applications.
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 uint
can hold
values from 0 through 4,294,967,295.
That 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 best seller, 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
!).
Throughout this book, we will use int
wherever it works, even if short
or byte
might be workable alternatives.
Memory is cheap, and programmer time expensive. There are
circumstances where the difference in memory usage would be
significant (for example, if you are going to hold a billion of them
in memory), but we’ll keep things simple by using the int
type whenever possible.
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. (The
"Variables" section
discusses how you tell it otherwise.)
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 ('
'
). You’ll see escape sequences later in
this book, and their use will be explained in context.
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.
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.
You are well advised to treat warnings as errors . Stop what you are doing and 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:
Right-click on the project.
Click on the Compile tab.
Make sure the “Treat all warnings as errors” checkbox is checked or set the Warnings that you want to treat as errors using the drop-down boxes.
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 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 I 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 like 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, Dog
s 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 if 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, IntelliSense pops up a list of valid methods to help you, as shown in Figure 3-1.
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.