Chapter 4. Types and References: It’s 10:00. Do you know where your data is?

image with no caption

Data type, database, Lieutenant Commander Data... it’s all important stuff. Without data, your programs are useless. You need information from your users, and you use that to look up or produce new information, to give back to them. In fact, almost everything you do in programming involves working with data in one way or another. In this chapter, you’ll learn the ins and outs of C#’s data types, how to work with data in your program, and even figure out a few dirty secrets about objects (psstt... objects are data, too).

The variable’s type determines what kind of data it can store

There are fifteen types built into C#, and each one stores a different kind of data. You’ve already seen some of the most common ones, and you know how to use them. But there are a few that you haven’t seen, and they can really come in handy, too.

Types you’ll use all the time

It shouldn’t come as a surprise that int, string, bool, and float are the most common types.

  • int can store any whole number from -2,147,483,648 to 2,147,483,647.

    Note

    A whole number doesn’t have a decimal point.

  • string can hold text of any length (including the empty string "").

  • bool is a Boolean value—it’s either true or false.

  • float can store any decimal number from ±1.5 × 10–45 to ±3.4 × 1038 with up to 7 significant figures. That range looks weird and complicated, but it’s actually pretty simple. The “significant figures” part means the precision of the number: 35,048,410,000,000, 1,743,059, 14.43857, and 0.00004374155 all have seven significant figures. The 1038 thing means that you can store any number as large as 1038 (or 1 followed by 38 zeroes)—as long as it only has 7 or fewer significant figures. On the other end of the range, 10-45 means that you can store any number as small as 10-45 (or a decimal point followed by 45 zeroes followed by 1)... but, you guessed it, as long as it only has 7 or fewer significant figures.

    Note

    “float” is short for “floating point”—as opposed to a “fixed point” number, which always has the same number of decimal places.

More types for whole numbers

Once upon a time, computer memory was really expensive, and processors were really slow. And, believe it or not, if you used the wrong type, it could seriously slow down your program. Luckily, times have changed, and most of the time if you need to store a whole number you can just use an int. But sometimes you really need something bigger... and once in a while, you need something smaller, too. That’s why C# gives you more options:

Note

The “u” in uint stands for “unsigned”, which means it can’t be negative (so there’s no minus sign).

Note

A lot of times, if you’re using these types it’s because you’re solving a problem where it really helps to have the “wrapping around” effect that you’ll read about in a few minutes.

  • byte can store any whole number between 0 and 255.

  • sbyte can store any whole number from -128 to 127.

  • short can store any whole number from -32,768 to 32,767.

  • ushort can store any whole number from 0 to 65,535.

    Note

    The “u” stands for “unsigned”

  • uint can store any whole number from 0 to 4,294,967,295.

  • long can store any number between minus and plus 9 billion billion.

  • ulong can store any number between 0 and about 18 billion billion.

Types for storing really HUGE and really tiny numbers

Sometimes 7 significant figures just isn’t precise enough. And, believe it or not, sometimes 1038 isn’t big enough and 10-45 isn’t small enough. A lot of programs written for finance or scientific research run into these problems all the time, so C# gives us two more types:

  • double can store any number from ±5.0 × 10-324 to ±1.7 × 10308 with 15–16 significant digits.

    Note

    The double type is actually as common as float. A lot of people use it all the time, and rarely use float.

  • decimal can store any number from ±1.0 × 10-28 to ±7.9 × 1028 with 28–29 significant digits.

    Note

    When your program needs to deal with currency, you usually want to use a decimal to store the number.

    Note

    When you used the Value property in your numericUpDown control, you were using a decimal.

Literals have types, too

Note

A literal just means a number that you type into your code. So when you type “int i = 5;”, the 5 is a literal.

When you type a number directly into your C# program, you’re using a literal... and every literal is automatically assigned a type. You can see this for yourself—just enter this line of code that assigns the literal 14.7 to an int variable:

int myInt = 14.7;

Now try to build the program. You’ll get this:

image with no caption

That’s the same error you’ll get if you try to set an int equal to a double variable. What the IDE is telling you is that the literal 14.7 has a type—it’s a double. You can change its type to a float by sticking an F on the end (14.7F). And 14.7M is a decimal.

Note

If you try to assign a float literal to a double or a decimal literal to a float, the IDE will give you a helpful message reminding you to add the right suffix. Cool!

A few more useful built-in types

Sometimes you need to store a single character like Q or 7 or $, and when you do you’ll use the char type. Literal values for char are always inside single quotes ('x', '3'). You can include escape sequences in the quotes, too ('\n' is a line break, '\t' is a tab). You write an escape sequence in your C# code using two characters, but your program stores each escape sequence as a single character in memory.

Note

You’ll learn a lot more about how char and byte relate to each other in Chapter 9.

And finally, there’s one more important type: object. You’ve already seen how an object can inherit from another one, and that object can in turn inherit from yet a different object. At the top of every inheritance hierarchy is the object class—that’s a special type that every other object inherits from. That’s really useful, because it means that you can assign any value, variable, or object to an object variable.

Brain Power

You can use the Windows calculator to convert between decimal (normal, base-10) numbers and binary numbers (base-2 numbers written with only ones and zeroes)—put it in Scientific mode, enter a number, and click the Bin radio button to convert to binary. Then click Dec to convert it back. Now enter some of the upper and lower limits for the whole number types (like -32,768 and 255) and convert them to binary. Can you figure out why C# gives you those particular limits?

A variable is like a data to-go cup

All of your data takes up space in memory. (Remember the heap from last chapter?) So part of your job is to think about how much space you’re going to need whenever you use a string or a number in your program. That’s one of the reasons you use variables. They let you set aside enough space in memory to store your data.

Think of a variable like a cup that you keep your data in. C# uses a bunch of different kinds of cups to hold different kinds of data. And just like the different sizes of cups at the coffee shop, there are different sizes of variables, too.

image with no caption

Numbers that have decimal places are stored differently than whole numbers. You can handle most of your numbers that have decimal places using float, the smallest data type that stores decimals. If you need to be more accurate, use a double, and if you’re writing a financial application where you’ll be storing currency values, you’ll want to use the decimal type.

It’s not always about numbers, though. (You wouldn’t expect to get hot coffee in a plastic cup or cold coffee in a paper one.) The C# compiler also can handle characters and non-numeric types. The char type holds one character, and string is used for lots of characters “strung” together. There’s no set size for a string object, either. It expands to hold as much data as you need to store in it. The bool data type is used to store true or false values, like the ones you’ve used for your if statements.

image with no caption
image with no caption

10 pounds of data in a 5 pound bag

image with no caption

When you declare your variable as one type, that’s how your compiler looks at it. Even if the value is nowhere near the upper boundary of the type you’ve declared, the complier will see the cup it’s in, not the number inside. So this won’t work:

int leaguesUnderTheSea = 20000;

short smallerLeagues = leaguesUnderTheSea;

20,000 would fit into a short, no problem. But since leaguesUnderTheSea is declared as an int, the compiler sees it as int-sized and considers it too big to put in a short container. The compiler won’t make those translations for you on the fly. You need to make sure that you’re using the right type for the data you’re working with.

image with no caption

Even when a number is the right size, you can’t just assign it to any variable

Let’s see what happens when you try to assign a decimal value to an int variable.

  • ➊ Create a new project and add a button to it. Then add these lines to the button’s Click() method:

    decimal myDecimalValue = 10;
    int myIntValue = myDecimalValue;
    
    MessageBox.Show("The myIntValue is " + myIntValue);

    Do this

  • ➋ Try building your program. Uh-oh—you got an error that looks like this:

    image with no caption
  • ➌ Make the error go away by casting the decimal to an int. Once you change the second line so it looks like this, your program will compile and run:

    image with no caption

So what happened?

The compiler won’t let you assign a value to a variable if it’s the wrong type—even if that variable can hold the value just fine—because that’s the underlying cause behind an enormous number of bugs. When you use casting, you’re essentially making a promise to the compiler that you know the types are different, and that in this particular instance it’s okay for C# to cram the data into the new variable.

Note

Take a minute to flip back to the beginning of the last chapter and check out how you used casting when you passed the NumericUpDown. Value to the Talker Tester form.

When you cast a value that’s too big, C# will adjust it automatically

You’ve already seen that a decimal can be cast to an int. It turns out that any number can be cast to any other number. But that doesn’t mean the value stays intact through the casting. If you cast an int variable that’s set to 365 to a byte variable, 365 is too big for the byte. But instead of giving you an error, the value will just wrap around: for example, 256 cast to a byte will have a value of 0. 257 would be converted to 1, 258 to 2, etc., up to 365, which will end up being 109. And once you get back to 255 again, the conversion value “wraps” back to zero.

Wrap it yourself!

There’s no mystery to how casting “wraps” the numbers—you can do it yourself. Just pop up the Windows calculator, switch it to Scientific mode, and calculate 365 Mod 256 (using the “Mod” button, which does a modulo calculation). You’ll get 109.

image with no caption

Yes! The + operator converts for you.

What you’ve been doing is using the + operator, which does a lot of converting for you automatically—but it’s especially smart about it. When you use + to add a number or boolean to a string, then it’ll automatically convert that value to a string, too. If you use + (or *, / or -) with two different types, it automatically converts the smaller type to the bigger one.

Here’s an example:

image with no caption

Since an int can fit into a float but a float can’t fit into an int, the + operator converts myInt to a float before adding it to myFloat.

C# does some casting automatically

There are two important conversions that don’t require you to do the casting. The first is done automatically any time you use arithmetic operators, like in this example:

image with no caption

The other way C# converts types for you automatically is when you use the + operator to concatenate strings (which just means sticking one string on the end of another, like you’ve been doing with message boxes). When you use + to concatenate a string with something that’s another type, it automatically converts the numbers to strings for you. Here’s an example. The first two lines are fine, but the third one won’t compile.

long x = 139401930;

MessageBox.Show("The answer is " + x);

MessageBox.Show(x);

The C# compiler spits out an error that mentions something about invalid arguments (an argument is what C# calls the value that you’re passing into a method’s parameter). That’s because the parameter for MessageBox.Show() is a string, and this code passed a long, which is the wrong type for the method. But you can convert it to a string really easily by calling its ToString() method. That method is a member of every value type and object. (All of the classes you build yourself have a ToString() method that returns the class name.) That’s how you can convert x to something that MessageBox.Show() can use:

MessageBox.Show(x.ToString());

When you call a method, the variables must match the types of the parameters

Try calling MessageBox.Show(123)—passing MessageBox.Show() a literal (123) instead of a string. The IDE won’t let you build your program. Instead, it’ll show you an error in the IDE: “Argument ‘1’: cannot convert from ‘int’ to ‘string’.” Sometimes C# can do the conversion automatically – like if your method expects an int, but you pass it a short – but it can’t do that for ints and strings

But MessageBox.Show() isn’t the only method that will give you compiler errors if you try to pass it a variable whose type doesn’t match the parameter. All methods will do that, even the ones you write yourself. Go ahead and try typing this completely valid method into a class:

image with no caption

When the compiler gives you an “invalid arguments” error, it means that you tried to call a method with variables whose types didn’t match the method’s parameters.

It works just fine if you pass it what it expects (a bool)—call MyMethod(true) or MyMethod(false), and it compiles just fine.

But what happens if you pass it an integer or a string instead? The IDE gives you a similar error to the one that you got when you passed 123 to MessageBox.Show(). Now try passing it a boolean, but assigning the return value to a string or passing it on to MessageBox.Show(). That won’t work, either—the method returns an int, not a long or the string that MessageBox.Show() expects.

Note

You can assign anything to a variable, parameter, or field with the type object.

Combining = with an operator

Take a good look at the operator we used to subtract ending mileage from starting mileage (-=). The problem is it doesn’t just subtract, it also assigns a value to the variable on the left side of the subtraction sign. The same thing happens in the line where we multiply number of miles traveled by the reimbursement rate. We should replace the -= and the *= with just - and *:

image with no caption

So can good variable names help you out here? Definitely! Take a close look at what each variable is supposed to do. You already get a lot of clues from the name milesTraveled—you know that’s the variable that the form is displaying incorrectly, and you’ve got a good idea of how that value ought to be calculated. So you can take advantage of that when you’re looking through your code to try to track down the bug. It’d be a whole lot harder to find the problem if the incorrect lines looked like this instead:

image with no caption

Objects use variables, too

So far, we’ve looked at objects separate from other types. But an object is just another data type. Your code treats objects exactly like it treats numbers, strings, and booleans. It uses variables to work with them:

Using an int

Using an object

  • ➊ Write a statement to declare the integer.

    int myInt;
  • ➋ Assign a value to the new variable.

    myInt = 3761;
  • ➌ Use the integer in your code.

    while (i < myInt) {
  • ➊ Write a statement to declare the object.

    image with no caption
  • ➋ Assign a value to the object.

    spot = new Dog();
  • ➌ Check one of the object’s fields.

    while (spot.Happy) {
image with no caption

Objects are just one more type of variable your program can use.

If your program needs to work with a whole number that’s really big, use a long. If it needs a whole number that’s small, use a short. If it needs a yes/no value, use a boolean. And if it needs something that barks and sits, use a Dog. No matter what type of data your program needs to work with, it’ll use a variable.

Refer to your objects with reference variables

When you create a new object, you use code like new Guy. But that’s not enough; even though that code creates a new Guy object on the heap, it doesn’t give you a way to access that object. You need a reference to the object. So you create a reference variable: a variable of type Guy with a name, like Joe. So Joe is a reference to the new Guy object you created. Anytime you want to use that particular guy, you can reference it with the reference variable called Joe.

Note

That’s called instantiating the object

So when you have a variable that is an object type, it’s a reference variable: a reference to a particular object. Take a look:

image with no caption

References are like labels for your object

In your kitchen, you probably have a container of salt and sugar. If you switched their labels, it would make for a pretty disgusting meal—even though the labels changed, the contents of the containers stayed the same. References are like labels. You can move labels around, point them at different things, but it’s the object that dictates what methods and data are available, not the reference itself.

image with no caption

When your code needs to work with an object in memory, it uses a reference, which is a variable whose type is a class of the object it’s going to point to. A reference is like a label that your code uses to talk about a specific object.

You never refer to your object directly. For example, you can’t write code like Guy.GiveCash() if Guy is your object type. The C# compiler doesn’t know which Guy you’re talking about, since you might have several instances of Guy on the heap. So you need a reference variable, like joe, that you assign to a specific instance, like Guy joe = new Guy().

Now, you can call methods, like joe.GiveCash(). joe refers to a specific instance of the Guy class, and your C# compiler knows exactly which instance to use. And, as you saw above, you might have multiple labels pointing to the same instance. So you could say Guy dad = joe, and then call dad.GiveCash(). That’s okay, too—that’s what Joe’s kid does every day.

If there aren’t any more references, your object gets garbage collected

If all of the labels come off of an object, no programs can access that object anymore. That means C# can mark the object for garbage collection. That’s when C# gets rid of any unreferenced objects, and reclaims the memory those objects took up for your program’s use.

For an object to stay in the heap, it has to be referenced. When the last reference to the object disappears, so does the object.

  • Here’s some code that creates an object.

    image with no caption
  • Now let’s create a second object.

    Guy bob = new Guy()
     { Name = "Bob", Cash = 75 };
    image with no caption
  • Let’s take the reference to the first object, and change it to point at the second object.

    image with no caption

Typecross

Take a break, and sit back and give your right brain something to do. It’s your standard crossword; all of the solution words are from this chapter.

When you’re done, turn the page, and take on the rest of the chapter.

image with no caption

Across

Down

1. The second part of a variable declaration

4. “namespace”, “for”, “while”, “using” and “new” are examples of _____________ words.

6. What (int) does in this line of code: x = (int) y;

8. When an object no longer has any references pointing to it, it’s removed from the heap using ____________ collection.

10. What you’re doing when you use the + operator to stick two strings together.

14. The type that holds the biggest numbers.

15. The type that stores a single letter or number

16. \n and \r are _______ sequences

17. The four whole number types that only hold positive numbers

2. You can combine the variable declaration and the _________ into one statement.

3. A variable that points to an object

5. What your program uses to work with data that’s in memory

7. If you want to store a currency value, use this type

9. += and -= are this kind of operator

11. A variable declaration always starts with this.

12. Every object has this method that converts it to a string.

13. When you’ve got a variable of this type, you can assign any value to it

Answers in Typecross Solution.

Multiple references and their side effects

You’ve got to be careful when you start moving around reference variables. Lots of times, it might seem like you’re simply pointing a variable to a different object. But, you could end up removing all references to another object in the process. That’s not a bad thing, but it may not be what you intended. Take a look:

  • Dog rover = new Dog();
    rover.Breed = "Greyhound";

    Objects: 1

    References: 1

    image with no caption
  • Dog fido = new Dog();
    fido.Breed = "Beagle";
    Dog spot = rover;

    Objects: 2

    References: 3

    image with no caption
  • Dog lucky = new Dog();
    lucky.Breed = "Dachshund";
    fido = rover;

    Objects: 2

    References: 4

    image with no caption

Brain Power

Why do you think we didn’t add a Swap() method to the Elephant class?

Two references means TWO ways to change an object’s data

Besides losing all the references to an object, when you have multiple references to an object, you can unintentionally change an object. In other words, one reference to an object may change that object, while another reference to that object has no idea that something has changed. Watch:

Do this

image with no caption
  • ➊ Add another button to your form.

  • ➋ Add this code for the button. Can you guess what’s going to happen when you click it?

    image with no caption
  • ➌ OK, go ahead and click the new button. Wait a second, that’s the Lucinda messagebox. Didn’t we call the WhoAmI() method from Lloyd?

    image with no caption

A special case: arrays

If you have to keep track of a lot of data of the same type, like a list of heights or a group of dogs, you can do it in an array. What makes an array special is that it’s a group of variables that’s treated as one object. An array gives you a way of storing and changing more than one piece of data without having to keep track of each variable individually. When you create an array, you declare it just like any other variable, with a name and a type:

image with no caption

Use each element in an array like it is a normal variable

When you use an array, first you need to declare a reference variable that points to the array. Then you need to create the array object using the new statement, specifying how big you want the array to be. Then you can set the elements in the array. Here’s an example of code that declares and fills up an array—and what’s happening on the heap when you do it. The first element in the array has an index of zero.

image with no caption

Arrays can contain a bunch of reference variables, too

You can create an array of object references just like you create an array of numbers or strings. Arrays don’t care what the type of variable is that they store; it’s up to you. So you can have an array of ints, or an array of Duck objects, with no problem.

Here’s code that creates an array of 7 Dog variables. The line that initializes the array only creates reference variables. Since there are only two new Dog() lines, only two actual instances of the Dog class) are created.

When you set or retrieve an element from an array, the number inside the brackets is called the index. The first element in the array has an index of zero.

image with no caption

An array’s length

You can find out how many elements are in an array using its Length property. So if you’ve got an array called heights, then you can use heights. Length to find out how long it is. If there are 7 elements in the array, that’ll give you 7—which means the array elements are numbered 0 to 6.

Welcome to Sloppy Joe’s Budget House o’ Discount Sandwiches!

Sloppy Joe has a pile of meat, a whole lotta bread, and more condiments than you can shake a stick at. But what he doesn’t have is a menu! Can you build a program that makes a new random menu for him every day?

Do this

image with no caption
  • Start a new project and add a MenuMaker class

    If you need to build a menu, you need ingredients. And arrays would be perfect for those lists. We’ll also need some way of choosing random ingredients to combine together into a sandwich. Luckily, the .NET Framework has a built-in class called Random that generates random numbers. So we’ll have four fields in our class: a Randomizer field that holds a reference to a Random object, and three arrays of strings to hold the meats, condiments, and breads.

    image with no caption
  • Add a GetMenuItem() method to the class that generates a random sandwich

    The point of the class is to generate sandwiches, so let’s add a method to do exactly that. It’ll use the Random object’s Next() method to choose a random meat, condiment and bread from each array. When you pass an int parameter to Next(), the method returns a random that’s less than that parameter. So if your Random object is called Randomizer, then calling Randomizer.Next(7) will return a random number between 0 and 6.

    So how do you know what parameter to pass into the Next() method? Well, that’s easy—just pass in each array’s Length. That will return the index of a random item in the array.

    image with no caption
  • Build your form

    Add six labels to the form, label1 through label6. Then add code to set each label’s Text property using a MenuMaker object. You’ll need to initialize the object using a new instance of the Random class. Here’s the code:

    image with no caption

Objects use references to talk to each other

So far, you’ve seen forms talk to objects by using reference variables to call their methods and check their fields. Objects can also call each others’ methods using references, too. In fact, there’s nothing that a form can do that your objects can’t do, because your form is just another object. And when objects talk to each other, one useful keyword that they have is this. Any time an object uses the this keyword, it’s referring to itself—it’s a reference that points to the object that calls it.

  • Here’s a method to tell an elephant to speak

    Let’s add a method to the Elephant class. Its first parameter is a message from an elephant. Its second parameter is the elephant that said it:

    public void TellMe(string message, Elephant whoSaidIt) {
        MessageBox.Show(whoSaidIt.Name + " says: " + message);
    }

    Here’s what it looks like when it’s called:

    Elephant lloyd = new Elephant() { Name = "Lloyd", EarSize = 40 };
    Elephant lucinda = new Elephant() { Name = "Lucinda", EarSize = 33 };
    lloyd.TellMe("Hi", lucinda);

    We called Lloyd’s TellMe() method, and passed it two parameters: “Hi” and a reference to Lucinda’s object. The method uses its whoSaidIt parameter to access the Name parameter of whatever elephant was passed into TellMe() using its second parameter.

  • Here’s a method that calls another method

    Now let’s add this SpeakTo() method to the Elephant class. It uses a special keyword: this. That’s a reference that lets an object talk about itself.

    image with no caption

    Let’s take a closer look at how this works.

    lucinda.SpeakTo(lloyd, "Hello");

    When Lucinda’s SpeakTo() method is called, it uses its talkTo reference parameter to call Lloyd’s TellMe() method.

    image with no caption

    So Lloyd acts as if he was called with (“Hello”, lucinda), and shows this message:

    image with no caption

Where no object has gone before

There’s another important keyword that you’ll use with objects. When you create a new reference and don’t set it to anything, it has a value. It starts off set to null, which means it’s not pointing to anything.

image with no caption

Any time you’ve got code in an object that’s going to be instantiated, the instance can use the special this variable that has a reference to itself.

Typecross Solution

image with no caption

Get Head First C# now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.