Chapter 2

Living with Variability — Declaring Value-Type Variables

IN THIS CHAPTER

Bullet Using C# variables, such as integers, as storage lockers

Bullet Declaring other types of variables — dates, characters, strings

Bullet Handling numeric constants

Bullet Changing types and letting the compiler figure out the type

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. However, these limits are in place for a reason. They make it easier for C# to understand what you mean by a particular kind of variable and for you to find mistakes in your code. 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#.

Declaring a Variable

Mathematicians work with numbers in a precise manner, but in a way that C# could never understand. The mathematician is free to introduce the variables as needed to present an idea in a particular way. Mathematicians use algorithms, a set of procedural steps used to solve a problem, in a way that makes sense to other mathematicians to model real-world needs. Algorithms can appear quite complex, even to other humans, much less C# (it doesn’t understand algorithms except what you tell it in code). For example, the mathematician may say this:

x = y2 + 2y + 1
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. A programmer must tell C# the kind of value that a variable contains and then tell C# specifically what to place in that variable in a manner that C# understands. 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.”

Remember The equals symbol (=) is called the assignment operator.

Technicalstuff 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 easier for C# to understand.) C# operators, such as the assignment operator, 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. You discover more about operators in Chapter 4 of this minibook.

What’s an int?

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 whole numbers –1, –2, –3, and so on.

Remember Before you can use a variable, you must declare it, which means creating a variable with a specific name (label) using code and optionally assigning a value to that variable. After you declare a variable as int, it can hold 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, so this locker is essentially empty. The second declaration not only declares an int variable m but also initializes it with a value of 2, all in one shot.

Remember 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. It’s always an error to use a variable before you initialize it.

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 m doesn't lose its value when you assign its value to n. It’s like cloning m.)

Rules for declaring variables

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.

Tip 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. For example, C# does place a 0 into an uninitialized int variable, but the compiler will still display an error if you try to use the variable before you initialize it. You may declare variables anywhere (well, almost anywhere) within a program.

Warning 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 n = 1;
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).

Variations on a theme: Different types of int

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

Type

Bytes

Range of Values

In Use

sbyte

1

–128 to 127

sbyte sb = 12;

byte

1

0 to 255

byte b = 12;

short

2

–32,768 to 32,767

short sh = 12345;

ushort

2

0 to 65,535

ushort ush = 62345;

int

4

–2,147,483,648 to 2,147,483,647

int n = 1234567890;

uint

4

0 to 4,294,967,295

uint un = 3234567890U;

long

8

–9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

long l = 123456789012L;

ulong

8

0 to 18,446,744,073,709,551,615

ulong ul = 123456789012UL;

As explained in the section entitled “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, unless the value won't fit in an int, in which case the compiler automatically selects the next largest type. 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.

Technicalstuff C# 9.0 and above also support a new native integer type, nint, that you use as nint MyInt = 9. The unsigned version appears as nuint. Native integer values are useful for low-level programming, such as when you want to interact with the Windows operating system directly. You won't use them in this book. Should you want to know more about native integers, the article “Native-sized integers - C# 9.0 specification proposals | Microsoft Docs (https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/native-integers) tells you more about them.

Representing Fractions

Integers are useful for most calculations. However, 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.

Okay, try a different value: 100 degrees Fahrenheit. Working through the equation, 100–32 is 68; 68 times 5 is 340; 340 / 9 is 37 when using integers. However, a closer answer is 37.78. Even that’s wrong because it’s really 37.777 … with the 7s repeating forever.

Remember 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.

Technicalstuff 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-sleeved 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.

Handling Floating-Point Variables

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?)

Remember Note that 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. You can find a description of the decimal type in the section “Using the Decimal Type: Is It an Integer or a Float?” later in this chapter.

Declaring a floating-point variable

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 lifetime.

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

float

8

1.5 * 10–45 to 3.4 * 1038

6 to 7

float f = 1.2F;

double

16

5.0 * 10–324 to 1.7 * 10308

15 to 16

double d = 1.2;

Remember You might think that 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 that 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.

Tip Use double variable types unless you have a specific reason to do otherwise. For example, here's the equation for converting from Fahrenheit to Celsius temperatures using floating-point variables:

double celsius = (fahr - 32.0) * (5.0 / 9.0);

Examining some limitations of floating-point variables

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 you discover in the following sections.

Counting

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?

Remember Regardless of whether you find that argument convincing, you can’t use a floating-point variable when counting things.

Comparing numbers

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.”

Technicalstuff Modern processors play a trick to make this problem less troublesome than it otherwise may be: They perform floating-point arithmetic in an especially long double format — that is, rather than use 64 bits, they use a whopping 80 bits (or 128-bits in newer processors). 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.

Calculation speed

Integers are always faster than floats to use because integers are less complex. Just as you can calculate the value of something using whole numbers a lot faster than using those pesky decimals, so can processors work faster with integers.

Technicalstuff Intel processors perform integer math using an internal structure called a general-purpose register that can work only with integers. These same registers are used for counting. Using general-purpose registers is extremely fast. Floating-point numbers require use of a special area that can handle real numbers called the Arithmetic Logic Unit (ALU) and special floating-point registers that don't work for counting. Each calculation takes longer because of the additional handling that floating-point numbers require.

Unfortunately, modern processors are so complex that you can’t know precisely how much time you save by using integers. Just know that using integers is generally faster, but that you won’t actually see a difference unless you’re performing a long list of calculations.

Not-so-limited range

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 much of the time.

Warning 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.)

Using the Decimal Type: Is It an Integer or a Float?

As explained 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 both 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 — which represents a lot of zeros! And it does so without rounding problems unless you're dealing with extremely large numbers.

Declaring a decimal

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.)

Comparing decimals, integers, and floating-point types

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, as explained in Chapter 5 of this minibook.

The second problem with decimal variables is equally serious or even more so. Computations involving decimal values are significantly slower than those involving either simple integer or floating-point values. 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.

Examining the bool Type: Is It Logical?

Finally, here's a logical variable type, one that can help you get to the truth of the matter. The Boolean type bool can have two values: true or false.

Warning 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 direct 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”).

Checking Out Character Types

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 type

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 described in Chapter 5 of this minibook. Character variables do not suffer from rounding problems.

Warning The character variable includes no font information. So you may store in a char variable what you think is a perfectly good kanji (and it may well be) — but when you view the character, it can look like garbage if you're not looking at it through the eyes of the proper font.

Special chars

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 quotation mark, space, single quotation mark). 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.

TABLE 2-3 Special Characters

Character Constant

Value

' '

Newline

' '

Tab

''

Null character

' '

Carriage return

''

Backslash

The string type

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 quotation marks. 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 newline 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 line and 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. A computer processor understands only numbers, not letters. The letter A is actually the number 65 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"

Warning The string with no characters, written "" (two double quotation marks in a row), is a valid string, called an empty string (or sometimes a null string by those who like to be confusing). However, an empty string ("") is different from a null char (''), a real null string (where string s1 = null), and from a string containing any amount of space, even one (" ").

Tip A best practice is 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 explained in the following section. Chapter 3 of this minibook goes into much more detail about the string type.

What’s a Value Type?

Technicalstuff The variable types described 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. In addition, these kinds of variables are stored in a special location called the stack as actual values. You don't need to worry about the stack; you just need to know that it exists as a location in memory. This characteristic is why these types of variables are called value types.

Remember 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 an intrinsic nor a value type — 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. Think of a reference type as you would an address for a house. Knowing the address tells you the location of the house, but you must actually go to the address to find the physical house.

The programmer-defined types explained 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 types are so widely used.

Comparing string and char

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 quotation marks, as in this example:

'a'

On the other hand, you put double quotation marks 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;

Technicalstuff 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. You find these operations described in Chapter 3 of this minibook.

Calculating Leap Years: DateTime

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(2020, 1, 1);
bool isLeapYear = DateTime.IsLeapYear(thisYear.Year);

The result for 2020 is true, but for 2021, it's false. (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; and 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, 2021
DayOfWeek dayOfWeek = DateTime.Now.DayOfWeek; // For example, Sunday

If you print that DayOfWeek object, it prints something like “Sunday.” And you can do other handy manipulations of DateTimes:

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 length 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 (length of time) to a DateTime. A duration 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, the following gives 1:00:00 AM:
DateTime anHourAfterMidnight = DateTime.Today.Add(duration1);
Console.WriteLine("An hour after 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).

Declaring Numeric Constants

There are very few absolutes in life; however, C# does have an 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 between the values of –2,147,483,648 to 2,147,483,647 is assumed to be an int. Numbers larger than 2,147,483,647 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

1

int

1U

unsigned int

1L

long int (avoid lowercase l; it's too much like the digit 1)

1.0

double

1.0F

float

1M

decimal

true

bool

false

bool

'a'

char

' '

char (the character newline)

'x123'

char (the character whose numeric value is hex 123)1

"a string"

string

""

string (an empty string); same as String.Empty

1 “hex” is short for hexadecimal (numbers in base 16 rather than in base 10).

Changing Types: The Cast

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.

Tip 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 forces C# to convert the long named longValue into an int and assumes that you know what you're 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.

Technicalstuff 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. Later in the book, you learn about the Convert class that goes a long way toward allowing you to convert anything into anything else, including converting bool values into int values, but using the Convert class isn't the same as performing a cast.

Letting the C# Compiler Infer Data Types

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.

Technicalstuff 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, that 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 the additional terms in x's initialization expression end up as double types. 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>;

Technicalstuff 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 — and 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.

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. Here’s what you see in the console window:

System.String

The output from this code proves that the compiler correctly inferred the type of aString.

Tip Most of the time, the best practice is to not 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.

You see examples later in which var is definitely called for, and you 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.

Tip 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 2. Anonymous? Bet you can’t wait.

What’s more, the dynamic type takes var a step further. The var type causes the compiler to infer the type of the variable based on the initialization value. The dynamic keyword does this at runtime, using a set of tools called the Dynamic Language Runtime. You can find more about the dynamic type in Chapter 6 of Book 3.

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

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