[go: up one dir, main page]

0% found this document useful (0 votes)
5 views18 pages

C#02

This document provides an overview of variables in C#, detailing their definition, creation, and usage. It explains the importance of variable types, naming conventions, and the distinction between different data types, including integers and strings. Additionally, it emphasizes best practices for naming variables to enhance code readability and maintainability.

Uploaded by

kcia1100011
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views18 pages

C#02

This document provides an overview of variables in C#, detailing their definition, creation, and usage. It explains the importance of variable types, naming conventions, and the distinction between different data types, including integers and strings. Additionally, it emphasizes best practices for naming variables to enhance code readability and maintainability.

Uploaded by

kcia1100011
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 18

LEVEL

VARIABLES

Speedrun
• A variable is a named location in memory for storing data.
• Variables have a type, a name, and a value (contents).
• Variables are declared (created) like this: .
• Assigning values to variables is done with the assignment operator:
• Using a variable name in an expression will copy the value out of the variable.
• Give your variables good names. You will be glad you did.

In this level, we will look at variables in more depth. We will also look at some rules around
good variable names.

WHAT IS A VARIABLE?
A crucial part of building software is storing data in temporary memory to use later. For
example, we might store a player’s current score or remember a menu choice long enough to
respond to it. When we talk about memory and variables, we are talking about “volatile”
memory (or RAM) that sticks around while your program runs but is wiped out when your
program closes or the computer is rebooted. (To let data survive longer than the program, we
must save it to persistent storage in a file, which is the topic of Level 39.)
A computer’s total memory is gigantic. Even my old smartphone has 3 gigabytes of memory—
large enough to store 750 million different numbers. Each memory location has a unique
numeric memory address, which can be used to access any specific location’s contents. But
remembering what’s in spot #45387 is not practical. Data comes and goes in a program. We
might need something for a split second or the whole time the program is running. Plus, not
all pieces of data are the same size. The text “Hello, World!” takes up more space than a single
number does. We need something smarter than raw memory addresses.
CREATING AND USING VARIABLES IN C# 33

A variable solves this problem for us. Variables are named locations where
data is stored in memory. Each variable has three parts: its name, type, and
contents or value. A variable’s type is important because it lets us know how
many bytes to reserve for it in memory, and it also allows the compiler to
ensure that we are using its contents correctly.
The first step in using a variable is to declare it. Declaring a variable allows
the computer to reserve a spot for it in memory of the appropriate size.
After declaring a variable, you can assign values or contents to the variable. The first time you
assign a value to a variable is called initializing it. Before a variable is initialized, it is
impossible to know what bits and bytes might be in that memory location, so initialization
ensures we only work with legitimate data.
While you can only declare a variable once, you can assign it different values over time as the
program runs. A variable for the player’s score can update as they collect points. The
underlying memory location remains the same, but the contents change with new values over
time.
The third thing you can do with a variable is retrieve its current value. The purpose of saving
the data was to come back to it later. As long as a variable has been initialized, we can retrieve
its current contents whenever we need it.

CREATING AND USING VARIABLES IN C#


The following code shows all three primary variable-related activities:
string username; // Declaring a variable
username = Console.ReadLine(); // Assigning a value to a variable
Console.WriteLine("Hi " + username); // Retrieving its current value

A variable is declared by listing its type and its name together ( ).


A variable is assigned a value by placing the variable name on the left side of an equal sign and
the new value on the right side. This new value may be an expression that the computer will
evaluate to determine the value ( ).
Retrieving the variable’s current value is done by simply using the variable’s name in an
expression ( ). In this case, your program will start by retrieving the
current value in . It then uses that value to produce the complete
message. The combined message is what is supplied to the method.
You can declare a variable anywhere within your code. Still, because variables must be
declared before they are used, variable declarations tend to gravitate toward the top of the
code.
Each variable can only be declared once, though your programs can create many variables.
You can assign new values to variables or retrieve the current value in a variable as often as
you want:
string username;

username = Console.ReadLine();
Console.WriteLine("Hi " + username);
34 LEVEL 5 VARIABLES
username = Console.ReadLine();
Console.WriteLine("Hi " + username);

Given that above is used to store two different usernames over time, it is
reasonable to reuse the variable. On the other hand, if the second value represents something
else—say a favorite color—then it is usually better to make a second variable:
string username;
username = Console.ReadLine();
Console.WriteLine("Hi " + username);

string favoriteColor;
favoriteColor = Console.ReadLine();
Console.WriteLine("Hi " + favoriteColor);

Remember that variable names are meant for humans to use, not the computer. Pick names
that will help human programmers understand their intent. The computer does not care.
Declaring a second variable technically takes up more space in memory, but spending a few
extra bytes (when you have billions) to make the code more understandable is a clear win.

INTEGERS
Every variable, value, and expression in your C# programs has a type associated with it. Before
now, the only type we have seen has been s (text). But many other types exist, and we
can even define our own types. Let’s look at a second type: , which represents an integer.
An integer is a whole number (no fractions or decimals) but either positive, negative, or zero.
Given the computer’s capacity to do math, it should be no surprise that storing numbers is
common, and many variables use the type. For example, all of these would be well
represented as an : a player’s score, pixel locations on a screen, a file’s size, and a country’s
population.
Declaring an -typed variable is as simple as using the type instead of the type
when we declare it:
int score;

This variable is now built to hold values instead of text.


This type concept is important, so I’ll state it again: types matter in C#; every value, variable,
and expression has a specific type, and the compiler will ensure that you don’t mix them up.
The following fails to compile because the types don’t match:
score = "Generic User"; // DOESN'T COMPILE!

The text is a , but ’s type is . This one is more subtle:


score = "0"; // DOESN'T COMPILE!

At least this looks like a number. But enclosed in quotes like that, is a string representation
of a number, not an actual number. It is a string literal, even though the characters could be
used in numbers. Anything in double quotes will always be a string. To make an int literal, you
write the number without the quote marks:
score = 0; // 0 is an int literal.
READING FROM A VARIABLE DOES NOT CHANGE IT 35

After this line of code runs, the variable—a memory location reserved to hold s
under the name —has a value of .
The following shows that you can assign different values to over time, as well as
negative numbers:
score = 4;
score = 11;
score = -1564;

READING FROM A VARIABLE DOES NOT CHANGE IT


When you read the contents of a variable, the variable’s contents are copied out. To illustrate:
int a;
int b;

a = 5;
b = 2;

b = a;
a = -3;

In the first two lines, and are declared and given an initial value (5 and 2, respectively),
which looks something like this:

On that fifth line, , the contents of are copied out of and replicated into .

The variables and are distinct, each with its own copy of the data. does not mean
and are now always going to be equal! That symbol means assignment, not equality.
(Though and will be equal immediately after running that line until something changes.)
Once the final line runs, assigning a value of to , will be updated as expected, but
retains the it already had. If we displayed the values of and at the end of this program,
we would see that is and is .
There are some additional nuances to variable assignment, which we will cover in Level 14.

CLEVER VARIABLE TRICKS


Declaring and using variables is so common that there are some useful shortcuts to learn
before moving on.
36 LEVEL 5 VARIABLES

The first is that you can declare a variable and initialize it on the same line, like this:
int x = 0;

This trick is so useful that virtually all experienced C# programmers would use this instead of
putting the declaration and initialization on back-to-back lines.
Second, you can declare multiple variables simultaneously if they are the same type:
int a, b, c;

Third, variable assignments are also expressions that evaluate to whatever the assigned value
was, which means you can assign the same thing to many variables all at once like this:
a = b = c = 10;

The value of is assigned to , but is an expression that evaluates to , which is


then assigned to . evaluates to , and that value is placed in . The above
code is the same as the following:
c = 10;
b = c;
a = b;

In my experience, this is not very common, but it does have its uses.
And finally, while types matter, can display both strings and integers:
Console.WriteLine(42);

In the next level, we will introduce many more variable types. can
display every single one of them. That is, while types matter and are not interchangeable,
is built to allow it to work with any type. We will see how this works
and learn to do it ourselves in the future.

VARIABLE NAMES
You have a lot of control over what names you give to your variables, but the language has a
few rules:
1. Variable names must start with a letter or the underscore character ( ). But C# casts a wide
net when defining “letters”—almost anything in any language is allowed. and
are legitimate variable names, but and are not.
2. After the start, you can also use numeric digits ( through ).
3. Most symbols and whitespace characters are banned because they make it impossible for
the compiler to know where a variable name ends and other code begins. (For example,
is not allowed because the character is used for subtraction. The
compiler assumes this is an attempt to subtract something called from
something called .)
4. You cannot name a variable the same thing as a keyword. For example, you cannot call a
variable or , as those are reserved, special words in the language.
I also recommend the following guidelines for naming variables:
1. Accurately describe what the variable holds. If the variable contains a player’s score,
or are acceptable. But and are not descriptive enough.
VARIABLE NAMES 37

2. Don’t abbreviate or remove letters. You spend more time reading code than you do
writing it, and if you must decode every variable name you encounter, you’re doing
yourself a disservice. What did (or worse, plain ) stand for again? Plural scar?
Plastic Scrabble? No, just player score. Common acronyms like or are an
exception to this rule.
3. Don’t fret over long names. It is better to use a descriptive name than “save characters.”
With any half-decent IDE, you can use features like AutoComplete to finish long names
after typing just a few letters anyway, and skipping the meaningful parts of names makes
it harder to remember what it does.
4. Names ending in numbers are a sign of poor names. With a few exceptions, variables
named , , and , do not distinguish one from another well
enough. (If they are part of a set that ought to go together, they should be packaged that
way; see Level 12.)
5. Avoid generic catch-all names. Names like , , , and are too
vague to be helpful in most cases.
6. Make the boundaries between multi-word names clear. A name like is
easier to read than . Two conventions among C# programmers are
(or ) and (or ), which
are illustrated by the way their names are written. In the first, every word but the first starts
with a capital letter. In the second, all words begin with a capital letter. The big capital
letter in the middle of the word makes it look like a camel’s hump, which is why it has this
name. Most C# programmers use for variables and
for other things. I recommend sticking with that convention as you get started, but
the choice is yours.
Picking good variable names doesn’t guarantee readable code, but it goes a long way.

Knowledge Check Variables 25 XP


Check your knowledge with the following questions:
1. Name the three things all variables have.
2. True/False. Variables must always be declared before being used.
3. Can you redeclare a variable?
4. Which of the following are legal C# variable names? , , , ,
, , .

Answers: (1) name, type, value. (2) True. (3) No. (4) answer, value1, delete_me, PI.
LEVEL
THE C# TYPE SYSTEM

Speedrun
• Types of variables and values matter in C#. They are not interchangeable.
• There are eight integer types for storing integers of differing sizes and ranges: , , ,
, , , , and .
• The type stores single characters.
• The type stores longer text.
• There are three types for storing real numbers: , , and .
• The type stores truth values (true and false) used in logic.
• These types are the building blocks of a much larger type system.
• Using for a variable’s type tells the compiler to infer its type from the surrounding code, so you
do not have to type it out. (But it still has a specific type.)
• The class helps convert one type to another.

In C#, types of variables and values matter (and must match), but we only know about two
types so far. In this level, we will introduce a diverse set of types we can use in our programs.
These types are called built-in types or primitive types. They are building blocks for more
complex types that we will see later.

REPRESENTING DATA IN BINARY


Why do types matter so much?
Every piece of data you want to represent in your programs must be stored in the computer’s
circuitry, limited to only the 1’s and 0’s of binary. If we're going to store a number, we need a
scheme for using bits (a single 1 or 0) and bytes (a group of 8 bits and the standard grouping
size of bits) to represent the range of possible numbers we want to store. If we’re going to
represent a word, we need some scheme for using the bits and bytes to represent both letters
and sequences (strings) of letters. More broadly, anything we want to represent in a program
requires a scheme for expressing it in binary.
INTEGER TYPES 39

Each type defines its own rules for representing values in binary, and different types are not
interchangeable. You cannot take bits and bytes meant to represent an integer and reinterpret
those bits and bytes as a string and expect to get meaning out of it. Nor can you take bits and
bytes meant to represent text and reinterpret them as an integer and expect it to be
meaningful. They are not the same. There’s no getting around it.
That doesn’t mean that each type is a world unto itself that can never interact with the other
worlds. We can and will convert from one type to another frequently. But the costs associated
with conversion are not free, so we do it conscientiously rather than accidentally.
Notably, C# does not invent entirely new schemes and rules for most of its types. The
computing world has developed schemes for common types like numbers and letters, and C#
reuses these schemes when possible. The physical hardware of the computer also uses these
same schemes. Since it is baked into the circuitry, it can be fast.
The specifics of these schemes are beyond this book’s scope, but let’s do a couple of thought
experiments to explore.
Suppose we want to represent the numbers 0 through 10. We need to invent a way to describe
each of these numbers with only 0’s and 1’s. Step 1 is to decide how many bits to use. One bit
can store two possible states (0 and 1), and each bit you add after that doubles the total
possibilities. We have 11 possible states, so we will need at least 4 bits to represent all of them.
Step 2 is to figure out which bit patterns to assign to each number. 0 can be . 1 can be
. Now it gets a little more complicated. 2 is , and 3 is . (We’re counting in
binary if that happens to be familiar to you.) We’ve used up all possible combinations of the
two bits on the right and need to use the third bit. 4 is , 5 is , and so on, all the way
to 10, which is . We have some unused bit patterns. isn’t anything yet. We could go
all the way up to 15 without needing any more bits.
We have one problem: the computer doesn’t deal well with anything smaller than full bytes.
Not a big deal; we’ll just use a full byte of eight bits.
If we want to represent letters, we can do a similar thing. We could assign the letter A to
, B to , and so on. (C# actually uses two bytes for every character.)
If we want to represent text (a string), we can use our letters as a building block. Perhaps we
could use a full byte to represent how many letters long our text is and then use two bytes for
each letter in the word. This is tricky because short words need to use fewer bytes than longer
words, and our system has to account for that. But this would be a workable scheme.
We don’t have to invent these schemes for types ourselves, fortunately. The C# language has
taken care of them for us. But hopefully, this illustrates why we can’t magically treat an integer
and a string as the same thing. (Though we will be able to convert from one type to another.)

INTEGER TYPES
Let’s explore the basic types available in a C# program, starting with the types used to
represent integers. While we used the type in the previous level, there are eight different
types for working with integers. These eight types are called integer types or integral types.
Each uses a different number of bytes, which allows you to store bigger numbers using more
memory or store smaller numbers while conserving memory.
The type uses 4 bytes and can represent numbers between roughly -2 billion and +2
billion. (The specific numbers are in the table below.)
40 LEVEL 6 THE C# TYPE SYSTEM

In contrast, the type uses 2 bytes and can represent numbers between about -32,000
and +32,000. The type uses 8 bytes and can represent numbers between about -9
quintillion and +9 quintillion (a quintillion is a billion billion).
Their sizes and ranges tell you when you might choose or over . If memory
is tight and a ’s range is sufficient, you can use a . If you need to represent
numbers larger than an can handle, you need to move up to a , even at the cost of
more bytes.
The , , and types are signed types; they include a positive or negative sign and
store positive and negative values. If you only need positive numbers, you could imagine
shifting these ranges upward to exclude negative values but twice as many positive values.
This is what the unsigned types are for: , , and . Each of these uses the
same number of bytes as their signed counterpart, cannot store negative numbers, but can
store twice as many positive numbers. Thus ’s range is 0 to about 65,000, ’s range
is 0 to about 4 billion, and ’s range is 0 to about 18 quintillion.
The last two integer types are a bit different. The first is the type, using a single byte to
represent values from 0 to 255 (unsigned). While integer-like, the type is more often
used to express a byte or collection of bytes with no specific structure (or none known to the
program). The type has a signed counterpart, , representing values in the
range -128 to +127. The type is not used very often but makes the set complete.
The table below summarizes this information.
Name Bytes Allow Negatives Minimum Maximum
1 No 0 255
2 Yes -32,768 32,767
4 Yes -2,147,483,648 2,147,483,647
8 Yes -9,223,372,036,854,775,808 9,223,372,036,854,775,807
1 Yes -128 127
2 No 0 65,535
4 No 0 4,294,967,295
8 No 0 18,446,744,073,709,551,615

Declaring and Using Variables with Integer Types


Declaring variables of these other types is as simple as using their type names instead of
or , as we have done before:
byte aSingleByte = 34;
aSingleByte = 17;

short aNumber = 5039;


aNumber = -4354;

long aVeryBigNumber = 395904282569;


aVeryBigNumber = 13;

In the past, we saw that writing out a number directly in our code creates an literal. But
this brings up an interesting question. How do we create a literal that is a literal or a
literal?
For things smaller than an , nothing special is needed to create a literal of that type:
INTEGER TYPES 41

byte aNumber = 32;

The is an literal, but the compiler is smart enough to see that you are trying to store it
in a and can ensure by inspection that is within the allowed range for a . The
compiler handles it. In contrast, if you used a literal that was too big for a , you would get
a compiler error, preventing you from compiling and running your program.
This same rule also applies to , , and .
If your literal value is too big to be an , it will automatically become a literal, a
literal, or a literal (the first of those capable of representing the number you typed).
You will get a compiler error if you make a literal whose value is too big for everything. To
illustrate how these bigger literal types work, consider this code:
long aVeryBigNumber = 10000000000; // 10 billion would be a `long` literal.

You may occasionally find that you want to force a smaller number to be one of the larger
literal types. You can force this by putting a or (or both) at the end of the literal value:
ulong aVeryBigNumber = 10000000000U;
aVeryBigNumber = 10000000000L;
aVeryBigNumber = 10000000000UL;

A signifies that it is unsigned and must be either a or . indicates that the literal
must be a or a , depending on the size. A indicates that it must be a .
These suffixes can be uppercase or lowercase and in either order. However, avoid using a
lowercase because that looks too much like a .
You shouldn’t need these suffixes very often.

The Digit Separator


When humans write a long number like 1,000,000,000, we often use a separator like a comma
to make interpreting the number easier. While we can’t use the comma for that in C#, there is
an alternative: the underscore character ( ).
int bigNumber = 1_000_000_000;

The normal convention for writing numbers is to group them by threes (thousands, millions,
billions, etc.), but the C# compiler does not care where these appear in the middle of numbers.
If a different grouping makes more logical sense, use it that way. All the following are allowed:
int a = 123_456_789;
int b = 12_34_56_78_9;
int c = 1_2__3___4____5;

Choosing Between the Integer Types


With eight types for storing integers, how do you decide which one to use?
On the one hand, you could carefully consider the possible range of values you might want for
any variable and then pick the smallest (to save on memory usage) that can fit the intended
range. For example, if you need a player’s score and know it can never be negative, you have
cut out half of the eight options right there. If the player’s score may be in the hundreds of
thousands in any playthrough, you can rule out and because they’re not big
enough. That leaves you with only and . If you think a player’s score might
42 LEVEL 6 THE C# TYPE SYSTEM

approach 4 billion, you’d better use , but if scores will only reach a few million, then a
is safe. (You can always change a variable’s type and recompile your program if you got
it wrong—software is soft after all—but it is easier to have just been right the first time.)
The strategy of picking the smallest practical range for any given variable has merit, but it has
two things going against it. The first is that in modern programming, rarely does saving a single
byte of space matter. There is too much memory around to fret over individual bytes. The
second is that computers do not have hardware that supports math with smaller types. The
computer upgrades them to s and runs the math as s, forcing you to then go to the
trouble of converting the result back to the smaller type. The type is more convenient than
, , , and if you are doing many math operations.
Thus, the more common strategy is to use , , , or as necessary and only
use , , , and when there is a clear and significant benefit.

Binary and Hexadecimal Literals


So far, the integer literals we have written have all been written using base 10, the normal 10-
digit system humans typically use. But in the programming world, it is occasionally easier to
write out the number using either base 2 (binary digits) or base 16 (hexadecimal digits, which
are 0 through 9, and then the letters A through F).
To write a binary literal, start your number with a . For example:
int thirteen = 0b00001101;

For a hexadecimal literal, you start your number with :


int theColorMagenta = 0xFF00FF;

This example shows one of the places where this might be useful. Colors are often represented
as either six or eight hexadecimal digits.

TEXT: CHARACTERS AND STRINGS


There are more numeric types, but let’s turn our attention away from numbers for a moment
and look at representing single letters and longer text.
In C#, the type represents a single character, while our old friend represents
text of any length.
The type is very closely related to the integer types. It is even lumped into the integral
type banner with the other integer types. Each character of interest is given a number
representing it, which amounts to a unique bit pattern. The type is not limited to just
keyboard characters. The type uses two bytes to allow for 65,536 distinct characters. The
number assigned to each character follows a widely used standard called Unicode. This set
covers English characters and every character in every human-readable language and a whole
slew of other random characters and emoji. A literal is made by placing the character in
single quotes:
char aLetter = 'a';
char baseball = '⚾';
FLOATING-POINT TYPES 43

You won’t find too many uses for the esoteric characters. The console window doesn’t even
know how to display the baseball character above). Still, the diversity of characters available
is nice.
If you know the hexadecimal Unicode number for a symbol and would prefer to use that, you
can write that out after a :
char aLetter = '\u0061'; // An 'a'

The type aggregates many characters into a sequence to allow for arbitrary text to be
represented. The word “string” comes from the math world, where a string is a sequence of
symbols chosen from a defined set of allowed symbols, one after the other, of any length. It is
a word that the programming world has stolen from the math world, and most programming
languages refer to this idea as strings.
A literal is made by placing the desired text in double quotes:
string message = "Hello, World!";

FLOATING-POINT TYPES
We now return to the number world to look at types that represent numbers besides integers.
How do we represent 1.21 gigawatts or the special number π?
C# has three types that are called floating-point data types. These represent what
mathematicians call real numbers, encompassing integers and numbers with a decimal or
fractional component. While we cannot represent 3.1415926 as an integer (3 is the best we
could do), we can represent it as a floating-point number.
The “point” in the name refers to the decimal point that often appears when writing out these
numbers.
The “floating” part comes because it contrasts with fixed-point types. The number of digits
before and after the decimal point is locked in place with a fixed-point type. The decimal point
may appear anywhere within the number with a floating-point type. C# does not have fixed-
point types because they prevent you from efficiently using very large or very small numbers.
In contrast, floating-point numbers let you represent a specific number of significant digits
and scale them to be big or small. For example, they allow you to express the numbers
1,250,421,012.6 and 0.00000000000012504210126 equally well, which is something a fixed-
point representation cannot reasonably do.
With floating-point types, some of the bits store the significant digits, affecting how precise
you can be, while other bits define how much to scale it up or down, affecting the magnitudes
you can represent. The more bits you use, the more of either you can do.
There are three flavors of floating-point numbers: , , and . The
type uses 4 bytes, while uses twice that many (hence the “double”) at 8 bytes. The
type uses 16 bytes. While and follow conventions used across the
computing world, including in the computer’s circuitry itself, does not. That means
and are faster. However, uses most of its many bits for storing
significant figures and is the most precise floating-point type. If you are doing something that
needs extreme precision, even at the cost of speed, is the better choice.
All floating-point numbers have ranges that are so mind-boggling in size that you wouldn’t
want to write them out the typical way. The math world often uses scientific notation to
44 LEVEL 6 THE C# TYPE SYSTEM

compactly write extremely big or small numbers. A thorough discussion of scientific notation
is beyond the scope of this book, but you can think of it as writing the zeroes in a number as a
power of ten. Instead of 200, we could write 2×102. Instead of 200000, we could write 2×105. As
the exponent grows by 1, the number of total digits also increases by 1. The exponent tells us
the scale of the number.
The same technique can be used for very tiny numbers, though the exponent is negative.
Instead of 0.02, we could write 2×10-2. Instead of 0.00002, we could write 2×10-5.
Now imagine what the numbers 2×1020 and 2×10-20 would look like when written the
traditional way. With that image in your mind, let’s look at what ranges the floating-point types
can represent.
A can store numbers as small as 3.4×10-45 and as large as 3.4×1038. That is small enough
to measure quarks and large enough to measure the visible universe many times over. A
has 6 to 7 digits of precision, depending on the number, meaning it can represent the
number 10000 and the number 0.0001, but does not quite have the resolution to differentiate
between 10000 and 10000.0001.
A can store numbers as small as 5×10-324 and as large as 1.7×10308, with 15 to 16 digits
of precision.
A can store numbers as small as 1.0×10-28 and as large as 7.9×1028, with 28 to 29 digits
of precision.
I’m not going to write out all of those numbers in normal notation, but it is worth taking a
moment to imagine what they might look like.
All three floating-point representations are insane in size, but seeing the exponents, you
should have a feel for how they compare to each other. The type uses the fewest bytes,
and its range and precision are good enough for almost everything. The type can
store the biggest big numbers and the smallest small numbers with even more precision than
a . The type’s range is the smallest of the three but is the most precise and is
great for calculations where accuracy matters (like financial or monetary calculations).
The table below summarizes how these types compare to each other:
Type Bytes Range Digits of Precision Hardware Supported
4 ±1.0 × 10-45 to ±3.4 × 1038 7 Yes
-324 308
8 ±5 × 10 to ±1.7 × 10 15-16 Yes
-28 28
16 ±1.0 × 10 to ±7.9 × 10 28-29 No
Creating variables of these types is the same as any other type, but it gets more interesting
when you make , , and literals:
double number1 = 3.5623;
float number2 = 3.5623f;
decimal number3 = 3.5623m;

If a number literal contains a decimal point, it becomes a literal instead of an integer


literal. Appending an or onto the end (with or without the decimal point) makes it a
literal. Appending an or onto makes it into a literal. (The “m” is for “monetary”
or “money.” Financial calculations often need incredibly high precision.)
All three types can represent a bigger range than any integer type, so if you use an integer
literal, the compiler will automatically convert it.
THE BOOL TYPE 45

Scientific Notation
As we saw when we first introduced the range floating-point numbers can represent, really big
and really small numbers are more concisely represented in scientific notation. For example,
6.022×1023 instead of 602,200,000,000,000,000,000,000. (That number, by the way, is called
Avogadro’s Number—a number with special significance in chemistry.) The × symbol is not
one on a keyboard, so for decades, scientists have written a number like 6.022×1023 as
6.022e23, where the e stands for “exponent.” Floating-point literals in C# can use this same
notation by embedding an or in the number:
double avogadrosNumber = 6.022e23;

THE BOOL TYPE


The last type we will cover in this level is the type. The type might seem strange if
you are new to programming, but we will see its value before long. The type gets its name
from Boolean logic, which was named after its creator, George Boole. The type
represents truth values. These are used in decision-making, which we will cover in Level 9. It
has two possible options: and . Both of those are literals that you can write
into your code:
bool itWorked = true;
itWorked = false;

Some languages treat as nothing more than fancy s, with being the number
and being anything else. But C# delineates s from s because conflating the
two is a pathway to lots of common bug categories.
A could theoretically use just a single bit, but it uses a whole byte.

Challenge The Variable Shop 100 XP


You see an old shopkeeper struggling to stack up variables in a window display. “Hoo-wee! All these
variable types sure are exciting but setting them all up to show them off to excited new programmers
like yourself is a lot of work for these aching bones,” she says. “You wouldn’t mind helping me set up this
program with one variable of every type, would you?”
Objectives:
• Build a program with a variable of all fourteen types described in this level.
• Assign each of them a value using a literal of the correct type.
• Use to display the contents of each variable.

Challenge The Variable Shop Returns 50 XP


“Hey! Programmer!” It’s the shopkeeper from the Variable Shop who hobbles over to you. “Thanks to
your help, variables are selling like RAM cakes! But these people just aren’t any good at programming.
They keep asking how to modify the values of the variables they’re buying, and… well… frankly, I have no
clue. But you’re a programmer, right? Maybe you could show me so I can show my customers?”
Objectives:
46 LEVEL 6 THE C# TYPE SYSTEM

• Modify your Variable Shop program to assign a new, different literal value to each of the 14 original
variables. Do not declare any additional variables.
• Use to display the updated contents of each variable.

This level has introduced the 14 most fundamental types of C#. It may seem a lot to take in,
and you may still be wondering when to use one type over another. But don’t worry too much.
This level will always be here as a reference when you need it.
These are not the only possible types in C#. They are more like chemical elements, serving as
the basis or foundation for producing other types.

TYPE INFERENCE
Types matter greatly in C#. Every variable, value, and expression has a specific, known type.
We have been very specific when declaring variables to call out each variable’s type. But the
compiler is very smart. It can often look at your code and figure out (“infer”) what type
something is from clues and cues around it. This feature is called type inference. It is the
Sherlock Holmes of the compiler.
Type inference is used for many language features, but a notable one is that the compiler can
infer the type of a variable based on the code that it is initialized with. You don’t always need
to write out a variable’s type yourself. You can use the keyword instead:
var message = "Hello, World!";

The compiler can tell that is a , and therefore, must be


a for this code to work. Using tells the compiler, “You’ve got this. I know you can
figure it out. I’m not going to bother writing it out myself.”
This only works if you initialize the variable on the same line it is declared. Otherwise, there is
not enough information for the compiler to infer its type. This won’t work:
var x; // DOES NOT COMPILE!

There are no clues to facilitate type inference here, so the type inference fails. You will have to
fall back to using specific, named types.
In Visual Studio, you can easily see what type the compiler inferred by hovering the mouse
over the keyword until the tooltip appears, which shows the inferred type.
Many programmers prefer to use everywhere they possibly can. It is often shorter and
cleaner, especially when we start using types with longer names.
But there are two potential problems to consider with . The first is that the computer
sometimes infers the wrong type. These errors are sometimes subtle. The second problem is
that the computer is faster at inferring a variable’s type than a human. Consider this code:
var input = Console.ReadLine();

The computer can infer that is a since it knows returns s.


It is much harder for us humans to pull this information out of memory.
It is worse when the code comes from the Internet or a book because you don’t necessarily
have all of the information to figure it out. For that reason, I will usually avoid in this book.
THE CONVERT CLASS AND THE PARSE METHODS 47

I recommend that you skip and use specific types as you start working in C#. Doing this
helps you think about types more carefully. After some practice, if you want to switch to ,
go for it.
I want to make this next point very clear, so pay attention: a variable that uses still has a
specific type. It isn’t a mystery type, a changeable type, or a catch-all type. It still has a specific
type; we have just left it unwritten. This does not work:
var something = "Hello";
something = 3; // ERROR. Cannot store an int in a string-typed variable.

THE CONVERT CLASS AND THE PARSE METHODS


With 14 types at our disposal, we will sometimes need to convert between types. The easiest
way is with the class. The class is like the class—a thing in the
system that provides you with a set of tasks or capabilities that it can perform. The
class is for converting between these different built-in types. To illustrate:
Console.Write("What is your favorite number?");
string favoriteNumberText = Console.ReadLine();
int favoriteNumber = Convert.ToInt32(favoriteNumberText);
Console.Write(favoriteNumber + " is a great number!");

You can see that ’s method needs a as an input and gives back or
returns an as a result, converting the text in the process. The class has
methods to convert among the built-in types:
Method Name Target Type Method Name Target Type

Most of the names above are straightforward, though a few deserve some explanation. The
names are not a perfect match because the class is part of .NET’s Base Class Library,
which all .NET languages use. No two languages use the same name for things like and
.
The , , and types, use the word and the number of bits they use. For
example, a uses 16 bits (2 bytes), so converts to a . , ,
and do the same, just with .
The other surprise is that converting to a is instead of . But a
is considered “double precision,” and a is “single precision,” which is where
the name comes from.
All input from the console window starts as s. Many of our programs will need to
convert the user’s text to another type to work with it. The process of analyzing text, breaking
48 LEVEL 6 THE C# TYPE SYSTEM

it apart, and transforming it into other data is called parsing. The class is a great
starting point for parsing text, though we will also learn additional parsing tools over time.

Parse Methods
Some C# programmers prefer an alternative to the class. Many of these types have
a method to convert a string to the type. For example:
int number = int.Parse("9000");

Some people prefer this style over the mostly equivalent . I’ll generally
use the class in this book. But feel free to use this second approach if you prefer it.

Knowledge Check Type System 25 XP


Check your knowledge with the following questions:
1. True/False. The type can store any possible integer.
2. Order the following by how large their range is, from smallest to largest: , , , .
3. True/False. The type is signed.
4. Which can store higher numbers, or ?
5. What three types can store floating-point numbers?
6. Which of the options in question 5 can hold the largest numbers?
7. Which of the options in question 5 is the most precise?
8. What type does the literal value (including the quotes) have?
9. What type stores true or false values?

Answers: (1) false. (2) , , , . (3) false. (4) . (5) , ,


. (6) . (7) . (8) . (9) .

The following page contains a diagram that summarizes the C# type system. It includes
everything we have discussed in this level and quite a few other types and categories we will
discuss in the future.
THE CONVERT CLASS AND THE PARSE METHODS 49

You might also like