Visiting the train station — the C# variable as storage locker
Using integers — you can count on it
Handling fractional values — what's half a duck?
Declaring other types of variables — dates, characters, strings
Handling numeric constants — Π in the sky
Changing types — cast doesn't mean toss
Letting the compiler figure out the type — var magic
The most fundamental of all concepts in programming is that of the variable. A C# variable is like a small box in which you can store things, particularly numbers, for later use. (The term variable is borrowed from the world of mathematics.)
Unfortunately for programmers, C# places several limitations on variables — limitations that mathematicians don't have to consider. This chapter takes you through the steps for declaring, initializing, and using variables. It also introduces several of the most basic data types in C#.
When the mathematician says, "n is equal to 1," that means the term n is equivalent to 1 in some ethereal way. The mathematician is free to introduce variables in a willy-nilly fashion. For example, the mathematician may say this:
x = y2 + 2y + y if k = y + 1 then x = k2
Programmers must define variables in a particular way that's more demanding than the mathematician's looser style. For example, a C# programmer may write the following bit of code:
int n; n = 1;
The first line means, "Carve off a small amount of storage in the computer's memory and assign it the name n." This step is analogous to reserving one of those storage lockers at the train station and slapping the label n on the side. The second line says, "Store the value 1 in the variable n, thereby replacing whatever that storage location already contains." The train-locker equivalent is, "Open the train locker, rip out whatever happens to be in there, and shove a 1 in its place."
The mathematician says, "n equals 1." The C# programmer says in a more precise way, "Store the value 1 in the variable n
." (Think about the train locker, and you see why that's preferable.) C# operators tell the computer what you want to do. In other words, operators are verbs and not descriptors. The assignment operator takes the value on its right and stores it in the variable on the left. We say a lot more about operators in Chapter 3 of this minibook.
In C#, each variable has a fixed type. When you allocate one of those train lockers, you have to pick the size you need. If you pick an integer locker, for instance, you can't turn around and hope to stuff the entire state of Texas in it — maybe Rhode Island, but not Texas.
For the example in the preceding section of this chapter, you select a locker that's designed to handle an integer — C# calls it an int
. Integers are the counting numbers 1, 2, 3, and so on, plus 0 and the negative numbers −1, −2, −3, and so on.
Before you can use a variable, you must declare it. After you declare a variable as int
, it can hold and regurgitate integer values, as this example demonstrates:
// Declare a variable named n - an empty train locker. int n; // Declare an int variable m and initialize it with the value 2. int m = 2; // Assign the value stored in m to the variable n. n = m;
The first line after the comment is a declaration that creates a little storage area, n
, designed to hold an integer value. The initial value of n
is not specified until it is assigned a value. The second declaration not only declares an int
variable m
but also initializes it with a value of 2, all in one shot.
The term initialize means to assign an initial value. To initialize a variable is to assign it a value for the first time. You don't know for sure what the value of a variable is until it has been initialized. Nobody knows.
The final statement in the program assigns the value stored in m
, which is 2, to the variable n
. The variable n
continues to contain the value 2 until it is assigned a new value. (The variable n
doesn't lose its value when you assign its value to m
. It's like cloning n
.)
You can initialize a variable as part of the declaration, like this:
// Declare another int variable and give it the initial value of 1. int p = 1;
This is equivalent to sticking a 1 into that int
storage locker when you first rent it, rather than opening the locker and stuffing in the value later.
Initialize a variable when you declare it. In most (but not all) cases, C# initializes the variable for you — but don't rely on it to do that.
You may declare variables anywhere (well, almost anywhere) within a program.
However, you may not use a variable until you declare it and set it to some value. Thus the last two assignments shown here are not legal:
// The following is illegal because m is not assigned // a value before it is used. int m; n = m; // The following is illegal because p has not been // declared before it is used. p = 2; int p;
Finally, you cannot declare the same variable twice in the same scope (a function, for example).
Most simple numeric variables are of type int
. However, C# provides a number of twists to the int
variable type for special occasions.
All integer variable types are limited to whole numbers. The int
type suffers from other limitations as well. For example, an int
variable can store values only in the range from roughly −2 billion to 2 billion.
A distance of 2 billion inches is greater than the circumference of the Earth. In case 2 billion isn't quite large enough for you, C# provides an integer type called long
(short for long int
) that can represent numbers almost as large as you can imagine. The only problem with a long
is that it takes a larger train locker: A long
consumes 8 bytes (64 bits) — twice as much as a garden-variety 4-byte (32-bit) int
. C# provides several other integer variable types, as shown in Table 2-1.
Table 2-1. Size and Range of C# Integer Types
Bytes | Range of Values | In Use | |
---|---|---|---|
| 1 | −128 to 127 |
|
| 1 | 0 to 255 |
|
| 2 | −32,768 to 32,767 |
|
| 2 | 0 to 65,535 |
|
| 4 | −2 billion to 2 billion |
|
| 4 | 0 to 4 billion (exact values listed in the Cheat Sheet on this book's Web site) |
|
| 8 | −1020 to 1020 — "a whole lot" |
|
| 8 | 0 to 2 × 1020 |
|
As I explain in the section "Declaring Numeric Constants," later in this chapter, fixed values such as 1 also have a type. By default, a simple constant such as 1 is assumed to be an int
. Constants other than an int
must be marked with their variable type. For example, 123U is an unsigned integer, uint
.
Most integer variables are called signed, which means they can represent negative values. Unsigned integers can represent only positive values, but you get twice the range in return. As you can see from Table 2-1, the names of most unsigned integer types start with a u
, while the signed types generally don't have a prefix.
You don't need any unsigned integer versions in this book.
Integers are useful for most calculations. One of this book's authors made it into the sixth grade before he ever found out that anything else existed, and he still hasn't forgiven his sixth-grade teacher for starting him down the slippery slope of fractions.
Many calculations involve fractions, which simple integers can't accurately represent. The common equation for converting from Fahrenheit to Celsius temperatures demonstrates the problem, like this:
// Convert the temperature 41 degrees Fahrenheit. int fahr = 41; int celsius = (fahr - 32) * (5 / 9)
This equation works just fine for some values. For example, 41 degrees Fahrenheit is 5 degrees Celsius. "Correct, Mr. Davis," says Stephen's sixth-grade teacher.
Okay, try a different value: 100 degrees Fahrenheit. Working through the equation, 100–32 is 68; 68 times 5/9 is 37. "No," she says, "The answer is 37.78." Even that's wrong because it's really 37.777 . . . with the 7s repeating forever, but I don't push the point.
An int
can represent only integer numbers. The integer equivalent of 37.78 is 37. This lopping off of the fractional part of a number to get it to fit into an integer variable is called integer truncation.
Truncation is not the same thing as rounding. Truncation lops off the fractional part. Goodbye, Charlie. Rounding picks the closest integer value. Thus, truncating 1.9 results in 1. Rounding 1.9 results in 2.
For temperatures, 37 may be good enough. It's not like you wear short-sleeve shirts at 37.7 degrees but pull on a sweater at 37 degrees. But integer truncation is unacceptable for many, if not most, applications.
Actually, the problem is much worse than that. An int
can't handle the ratio 5/9 either; it always yields the value 0. Consequently, the equation as written in this example calculates celsius
as 0 for all values of fahr
. Even I admit that's unacceptable.
This book's Web site includes an int
-based temperature-conversion program contained in the ConvertTemperatureWithRoundOff
directory. At this point, you may not understand all the details, but you can see the conversion equations and execute the program ConvertTemperatureWithRoundOff.exe
to see the results. (Review Chapter 1 of this minibook if you need a hand running it.)
The limitations of an int
variable are unacceptable for some applications. The range generally isn't a problem — the double-zillion range of a 64-bit-long integer should be enough for almost anyone. However, the fact that an int
is limited to whole numbers is a bit harder to swallow.
In some cases, you need numbers that can have a nonzero fractional part. Mathematicians call these real numbers. (Somehow that always seemed like a ridiculous name for a number. Are integer numbers somehow unreal?)
Notice that I said a real number can have a nonzero fractional part — that is, 1.5 is a real number, but so is 1.0. For example, 1.0 + 0.1 is 1.1. Just keep that point in mind as you read the rest of this chapter.
Fortunately, C# understands real numbers. Real numbers come in two flavors: floating-point and decimal. Floating-point is the most common type. I describe the decimal type a little later in this chapter.
A floating-point variable carries the designation float
, and you declare one as shown in this example:
float f = 1.0;
After you declare it as float
, the variable f
is a float
for the rest of its natural instructions.
Table 2-2 describes the two kinds of floating-point types. All floating-point variables are signed. (There's no such thing as a floating-point variable that can't represent a negative value.)
Table 2-2. Size and Range of Floating-Point Variable Types
Type | Bytes | Range of Values | Accuracy to Number of Digits | In Use |
---|---|---|---|---|
| 8 | 1.5 * 10−45 to 3.4 * 1038 | 6 to 7 |
|
| 16 | 5.0 * 10−324 to 1.7 * 10308 | 15 to 16 |
|
You might think float
is the default floating-point variable type, but actually the double
is the default in C#. If you don't specify the type for, say, 12.3, C# calls it a double
.
The Accuracy column in Table 2-2 refers to the number of significant digits that such a variable type can represent. For example, 5/9 is actually 0.555 . . . with an unending sequence of 5s. However, a float
variable is said to have six significant digits of accuracy — which means numbers after the sixth digit are ignored. Thus 5/9 may appear this way when expressed as a float
:
0.5555551457382
Here you know that all the digits after the sixth 5 are untrustworthy.
The same number — 5/9 — may appear this way when expressed as a double
:
0.55555555555555557823
The double
packs a whopping 15 to 16 significant digits.
Use double
variable types unless you have a specific reason to do otherwise.
Here's the formula for converting from Fahrenheit to Celsius temperatures using floating-point variables:
double celsius = (fahr - 32.0) * (5.0 / 9.0)
The Web site contains a floating-point version of the temperature-conversion program called ConvertTemperatureWithFloat
.
The following example shows the result of executing the double
-based ConvertTemperatureWithFloat
program:
Enter temp in degrees Fahrenheit:100 Temperature in degrees Celsius = 37.7777777777778 Press Enter to terminate...
You may be tempted to use floating-point variables all the time because they solve the truncation problem so nicely. Sure, they use up a bit more memory. But memory is cheap these days, so why not? But floating-point variables also have limitations, which I discuss in the following sections.
You can't use floating-point variables as counting numbers. Some C# structures need to count (as in 1, 2, 3, and so on). You know that 1.0, 2.0, and 3.0 are counting numbers just as well as 1, 2, and 3, but C# doesn't know that. For example, given the accuracy limitations of floating-points, how does C# know that you aren't actually saying 1.000001?
Whether you find that argument convincing, you can't use a floating-point variable when counting things.
You have to be careful when comparing floating-point numbers. For example, 12.5 may be represented as 12.500001. Most people don't care about that little extra bit on the end. However, the computer takes things extremely literally. To C#, 12.500000 and 12.500001 are not the same numbers.
So, if you add 1.1 to 1.1, you can't tell whether the result is 2.2 or 2.200001. And if you ask, "Is doubleVariable
equal to 2.2?" you may not get the results you expect. Generally, you have to resort to some bogus comparison like this: "Is the absolute value of the difference between doubleVariable
and 2.2 less than .000001?" In other words, "within an acceptable margin of error."
The Pentium processor plays a trick to make this problem less troublesome than it otherwise may be: It performs floating-point arithmetic in an especially long double
format — that is, rather than using 64 bits, it uses a whopping 80 bits. When rounding off an 80-bit float
into a 64-bit float
, you (almost) always get the expected result, even if the 80-bit number was off a bit or two.
Processors such as the x86 varieties used in older Windows-based PCs could perform integer arithmetic much faster than arithmetic of the floating-point persuasion. In those days, programmers would go out of their way to limit a program to integer arithmetic.
The ratio in additional speed on a Pentium III processor for a simple (perhaps too simple) test of about 300,000,000 additions and subtractions was about 3 to 1. That is, for every double
add, you could have done three int
adds. (Computations involving multiplication and division may show different results.)
In the past, a floating-point variable could represent a considerably larger range of numbers than an integer type. It still can, but the range of the long
is large enough to render the point moot.
Even though a simple float
can represent a very large number, the number of significant digits is limited to about six. For example, 123,456,789F
is the same as 123,456,000F
. (For an explanation of the F
notation at the end of these numbers, see "Declaring Numeric Constants," later in this chapter.)
As I explain in previous sections of this chapter, both the integer and floating-point types have their problems. Floating-point variables have rounding problems associated with limits to their accuracy, while int
variables just lop off the fractional part of a variable. In some cases, you need a variable type that offers the best of two worlds:
Like a floating-point variable, it can store fractions.
Like an integer, numbers of this type offer exact values for use in computations — for example, 12.5 is really 12.5 and not 12.500001.
Fortunately, C# provides such a variable type, called decimal
. A decimal
variable can represent a number between 10−28 and 1028 — that's a lot of zeros! And it does so without rounding problems.
Decimal variables are declared and used like any variable type, like this:
decimal m1 = 100; // Good decimal m2 = 100M; // Better
The first declaration shown here creates a variable m1
and initializes it to a value of 100
. What isn't obvious is that 100 is actually of type int
. Thus, C# must convert the int
into a decimal
type before performing the initialization. Fortunately, C# understands what you mean — and performs the conversion for you.
The declaration of m2
is the best. This clever declaration initializes m2
with the decimal
constant 100M. The letter M at the end of the number specifies that the constant is of type decimal
. No conversion is required. (See the section "Declaring Numeric Constants," later in this chapter.)
The decimal
variable type seems to have all the advantages and none of the disadvantages of int
or double
types. Variables of this type have a very large range, they don't suffer from rounding problems, and 25.0 is 25.0 and not 25.00001.
The decimal
variable type has two significant limitations, however. First, a decimal
is not considered a counting number because it may contain a fractional value. Consequently, you can't use them in flow-control loops, which I explain in Chapter 5 of this minibook.
The second problem with decimal
variables is equally as serious or even more so. Computations involving decimal
values are significantly slower than those involving either simple integer or floating-point values — and I do mean significant. On a crude benchmark test of 300,000,000 adds and subtracts, the operations involving decimal
variables were approximately 50 times slower than those involving simple int
variables. The relative computational speed gets even worse for more complex operations. Besides that, most computational functions, such as calculating sines or exponents, are not available for the decimal
number type.
Clearly, the decimal
variable type is most appropriate for applications such as banking, in which accuracy is extremely important but the number of calculations is relatively small.
Finally, a logical variable type. Except in this case, I really mean a type "logical." The Boolean type bool
can have two values: true
or false
. I kid thee not — a whole variable type for just two values. Not even a "maybe."
Former C and C++ programmers are accustomed to using the int
value 0 (zero) to mean false
and nonzero to mean true
. That doesn't work in C#.
You declare a bool
variable this way:
bool thisIsABool = true;
No conversion path exists between bool
variables and any other types. In other words, you can't convert a bool
directly into something else. (Even if you could, you shouldn't because it doesn't make any sense.) In particular, you can't convert a bool
into an int
(such as false
becoming 0) or a string
(such as false
becoming the word "false").
A program that can do nothing more than spit out numbers may be fine for mathematicians, accountants, insurance agents with their mortality figures, and folks calculating cannon-shell trajectories. (Don't laugh. The original computers were built to generate tables of cannon-shell trajectories to help artillery gunners.) However, for most applications, programs must deal with letters as well as numbers.
C# treats letters in two distinctly different ways: individual characters of type char
(usually pronounced char, as in singe or burn) and strings of characters — a type called, cleverly enough, string
.
The char
variable is a box capable of holding a single character. A character constant appears as a character surrounded by a pair of single quotation marks, as in this example:
char c = 'a';
You can store any single character from the Roman, Hebrew, Arabic, Cyrillic, and most other alphabets. You can also store Japanese katakana and hiragana characters, as well as many Japanese and Chinese kanjis.
In addition, char
is considered a counting type. That means you can use a char
type to control the looping structures that I describe in Chapter 5 of this minibook. Character variables do not suffer from rounding problems.
Some characters within a given font are not printable, in the sense that you don't see anything when you look at them on the computer screen or printer. The most obvious example of this is the space, which is represented by the character ' '
(single quote, space, single quote). Other characters have no letter equivalent — for example, the tab character. C# uses the backslash to flag these characters, as shown in Table 2-3.
Another extremely common variable type is the string
. The following examples show how you declare and initialize string
variables:
// Declare now, initialize later. string someString1; someString1 = "this is a string"; // Or initialize when declared - preferable. string someString2 = "this is a string";
A string
constant, often called a string
literal, is a set of characters surrounded by double quotes. The characters in a string
can include the special characters shown in Table 2-3. A string
cannot be written across a line in the C# source file, but it can contain the new-line character, as the following examples show (see boldface):
// The following is not legal.
string someString = "This is a line
and so is this";
// However, the following is legal.
string someString = "This is a lineand so is this";
When written out with Console.WriteLine
, the last line in this example places the two phrases on separate lines, like this:
This is a line and so is this
A string
is not a counting type. A string
is also not a value-type — no "string" exists that's intrinsic (built in) to the processor. Only one of the common operators works on string
objects: The +
operator concatenates two strings into one. For example:
string s = "this is a phrase" + " and so is this";
These lines of code set the string
variable s
equal to this character string:
"this is a phrase and so is this"
The string
with no characters, written ""
(two double quotes in a row), is a valid string
, called an empty string
(or sometimes a null string
). A null string
(""
) is different from a null char
(' '
) and from a string
containing any amount of space, even one (" "
).
I like to initialize strings using the String.Empty
value, which means the same thing as ""
and is less prone to misinterpretation:
string mySecretName = String.Empty; // A property of the String type
By the way, all the other data types in this chapter are value types. The string
type, however, is not a value type, as I explain in the following section. Chapter 3 of this minibook goes into much more detail about the string
type.
The variable types that I describe in this chapter are of fixed length — again with the exception of string
. A fixed-length variable type always occupies the same amount of memory. So if you assign a = b
, C# can transfer the value of b
into a
without taking extra measures designed to handle variable-length types. This characteristic is why these types of variables are called value types.
The types int, double
, and bool
, and their close derivatives (like unsigned int
) are intrinsic variable types built right into the processor. The intrinsic variable types plus decimal
are also known as value types because variables store the actual data. The string
type is neither — because the variable actually stores a sort of "pointer" to the string's data, called a reference. The data in the string is actually off in another location.
The programmer-defined types that I explain in Chapter 8 of this minibook, known as reference types, are neither value types nor intrinsic. The string
type is a reference type, although the C# compiler does accord it some special treatment because string
s are so widely used.
Although strings deal with characters, the string
type is amazingly different from the char
. Of course, certain trivial differences exist. You enclose a character with single quotes, as in this example:
'a'
On the other hand, you put double quotes around a string:
"this is a string" "a" // So is this -- see the double quotes?
The rules concerning strings are not the same as those concerning characters. For one thing, you know right up front that a char
is a single character, and that's it. For example, the following code makes no sense, either as addition or as concatenation:
char c1 = 'a'; char c2 = 'b'; char c3 = c1 + c2
Actually, this bit of code almost compiles — but with a completely different meaning from what was intended. These statements convert c1
into an int
consisting of the numeric value of c1
. C# also converts c2
into an int
and then adds the two integers. The error occurs when trying to store the results back into c3
— numeric data may be lost storing an int
into the smaller char
. In any case, the operation makes no sense.
A string, on the other hand, can be any length. So concatenating two strings, as shown here, does make sense:
string s1 = "a"; string s2 = "b"; string s3 = s1 + s2; // Result is "ab"
As part of its library, C# defines an entire suite of string operations. I describe them in Chapter 3 of this minibook.
What if you had to write a program that calculates whether this year is a leap year?
The algorithm looks like this:
It's a leap year if year is evenly divisible by 4 and, if it happens to be evenly divisible by 100, it's also evenly divisible by 400
You don't have enough tools yet to tackle that in C#. But you could just ask the DateTime
type (which is a value type, like int
):
DateTime thisYear = new DateTime(2011, 1, 1); bool isLeapYear = DateTime.IsLeapYear(thisYear.Year);
The result for 2011 is false
, but for 2012, it's true
. (For now, don't worry about that first line of code, which uses some things you haven't gotten to yet.)
With the DateTime
data type, you can do something like 80 different operations, such as pull out just the month; get the day of the week; add days, hours, minutes, seconds, milliseconds, months, or years to a given date; get the number of days in a given month; subtract two dates.
The following sample lines use a convenient property of DateTime
called Now
to capture the present date and time, and one of the numerous DateTime
methods that let you convert one time into another:
DateTime thisMoment = DateTime.Now; DateTime anHourFromNow = thisMoment.AddHours(1);
You can also extract specific parts of a DateTime
:
int year = DateTime.Now.Year; // For example, 2007 DayOfWeek dayOfWeek = DateTime.Now.DayOfWeek; // For example, Sunday
If you print out that DayOfWeek
object, it prints something like "Sunday." And you can do other handy manipulations of DateTime
s:
DateTime date = DateTime.Today; // Get just the date part. TimeSpan time = thisMoment.TimeOfDay; // Get just the time part. TimeSpan duration = new TimeSpan(3, 0, 0, 0); // Specify duration in days. DateTime threeDaysFromNow = thisMoment.Add(duration);
The first two lines just extract portions of the information in a DateTime
. The next two lines add a duration to a DateTime
. A duration, or amount of time, differs from a moment in time; you specify durations with the TimeSpan
class, and moments with DateTime
. So the third line sets up a TimeSpan
of three days, zero hours, zero minutes, and zero seconds. The fourth line adds the three-day duration to the DateTime
representing right now, resulting in a new DateTime
whose day component is three greater than the day component for thisMoment
.
Subtracting a DateTime
from another DateTime
(or a TimeSpan
from a DateTime
) returns a DateTime
:
TimeSpan duration1 = new TimeSpan(1, 0, 0); // One hour later. // Since Today gives 12:00:00 AM (midnight), the following gives 1:00:00 AM: DateTime anHourAfterMidnight = DateTime.Today.Add(duration1); Console.WriteLine("An hour from midnight will be {0}", anHourAfterMidnight); DateTime midnight = anHourAfterMidnight.Subtract(duration1); Console.WriteLine("An hour before 1 AM is {0}", midnight);
The first line of the preceding code creates a TimeSpan
of one hour. The next line gets the date (actually, midnight this morning) and adds the one-hour span to it, resulting in a DateTime
representing 1:00 a.m. today. The next-to-last line subtracts a one-hour duration from 1:00 a.m. to get 12:00 a.m. (midnight).
For more information, search for DateTime structure in the Visual Studio Help system and take a look at the DateTimeExample
program on this book's Web site.
There are very few absolutes in life; however, I'm about to give you a C# absolute: Every expression has a value and a type. In a declaration such as int n
, you can easily see that the variable n
is an int
. Further, you can reasonably assume that the type of a calculation n + 1
is an int
. However, what type is the constant 1
?
The type of a constant depends on two things: its value and the presence of an optional descriptor letter at the end of the constant. Any integer type less than 2 billion is assumed to be an int
. Numbers larger than 2 billion are assumed to be long
. Any floating-point number is assumed to be a double
.
Table 2-4 demonstrates constants that have been declared to be of a particular type. The case of these descriptors is not important; 1U
and 1u
are equivalent.
Table 2-4. Common Constants Declared along with Their Types
Constant | Type |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
|
|
|
|
[a] "hex" is short for hexadecimal (numbers in base 16 rather than in base 10). |
Humans don't treat different types of counting numbers differently. For example, a normal person (as distinguished from a C# programmer) doesn't think about the number 1 as being signed, unsigned, short, or long. Although C# considers these types to be different, even C# realizes that a relationship exists between them. For example, this bit of code converts an int
into a long
:
int intValue = 10; long longValue; longValue = intValue; // This is OK.
An int
variable can be converted into a long
because any possible value of an int
can be stored in a long
— and because they are both counting numbers. C# makes the conversion for you automatically without comment. This is called an implicit type conversion.
A conversion in the opposite direction can cause problems, however. For example, this line is illegal:
long longValue = 10; int intValue; intValue = longValue; // This is illegal.
Some values that you can store in a long
don't fit in an int
(4 billion, for example). If you try to shoehorn such a value into an int
, C# generates an error because data may be lost during the conversion process. This type of bug is difficult to catch.
But what if you know that the conversion is okay? For example, even though longValue
is a long
, maybe you know that its value can't exceed 100 in this particular program. In that case, converting the long
variable longValue
into the int
variable intValue
would be okay.
You can tell C# that you know what you're doing by means of a cast:
long longValue = 10; int intValue; intValue = (int)longValue; // This is now OK.
In a cast, you place the name of the type you want in parentheses and put it immediately in front of the value you want to convert. This cast says, "Go ahead and convert the long
named longValue
into an int
— I know what I'm doing." In retrospect, the assertion that you know what you're doing may seem overly confident, but it's often valid.
A counting number can be converted into a floating-point number automatically, but converting a floating-point into a counting number requires a cast:
double doubleValue = 10.0; long longValue = (long)doubleValue;
All conversions to and from a decimal
require a cast. In fact, all numeric types can be converted into all other numeric types through the application of a cast. Neither bool
nor string
can be converted directly into any other type.
Built-in C# methods can convert a number, character, or Boolean into its string equivalent, so to speak. For example, you can convert the bool
value true
into the string
"true"; however, you cannot consider this change a direct conversion. The bool true
and the string
"true" are completely different things.
So far in this book — well, so far in this chapter — when you declared a variable, you always specified its exact data type, like this:
int i = 5; string s = "Hello C#"; double d = 1.0;
You're allowed to offload some of that work onto the C# compiler, using the var
keyword:
var
i = 5;var
s = "Hello C# 4.0";var
d = 1.0;
Now the compiler infers the data type for you — it looks at the stuff on the right side of the assignment to see what type the left side is.
For what it's worth, Chapter 3 of this minibook shows how to calculate the type of an expression like the ones on the right side of the assignments in the preceding example. Not that you need to do that — the compiler mostly does it for you. Suppose, for example, you have an initializing expression like this:
var x = 3.0 + 2 − 1.5;
The compiler can figure out that x
is a double
value. It looks at 3.0
and 1.5
and sees that they're of type double
. Then it notices that 2
is an int
, which the compiler can convert implicitly to a double
for the calculation. All of the addition terms in x
's initialization expression end up as double
s. So the inferred type of x
is double
.
But now, you can simply utter the magic word var
and supply an initialization expression, and the compiler does the rest:
var aVariable = <initialization expression here>;
If you've worked with a scripting language such as JavaScript or VBScript, you may have gotten used to all-purpose-in-one data types. VBScript calls them Variant
data types — a Variant
can be anything at all. But does var
in C# signify a Variant
type? Not at all. The object you declare with var
definitely has a C# data type, such as int, string
, or double
. You just don't have to declare what it is.
The UsingVarForImplicitTypeInference
example on the Web site demonstrates var
with several examples. Here's a taste.
What's really lurking in the variables declared in this example with var
? Take a look at this:
var aString = "Hello C# 3.0"; Console.WriteLine(aString.GetType().ToString());
The mumbo jumbo in that WriteLine
statement calls the String.GetType()
method on aString
to get its C# type. Then it calls the resulting object's ToString()
method to display the object's type. (Yadda yadda.) Here's what you see in the console window:
System.String
It proves that the compiler correctly inferred the type of aString
.
Most of the time, I recommend that you don't use var
. Save it for when it's necessary. Being explicit about the type of a variable is clearer to anyone reading your code than using var
. However, common usage in the C# world may change so much that everybody uses var
all the time, in spite of my good advice. In that case, you can go along with the crowd.
You see examples later in which var
is definitely called for, and I use it part of the time throughout this book, even sometimes where it's not strictly necessary. You need to see it used, and use it yourself, to internalize it. I'm still getting used to it myself. (I can't help it if I'm a slow learner.)
You can see var
used in other ways: with arrays and collections of data, in Chapter 6 of this minibook, and with anonymous types, in Book II. Anonymous? Bet you can't wait.
What's more, a new type in C# 4.0 is even more flexible than var:
The dynamic
type takes var
a step further.
The var
type causes the compiler to infer the type of the variable based on expected input. The dynamic
keyword does this at runtime, using a totally new set of tools called the Dynamic Language Runtime. You can find more about the dynamic
type in Book VIII.