Classes Part 1: Using Them

Classes Part 1: Using Them

The Crash Course

  • Object oriented programming is a way of programming where you create chunks of code that match up with real world objects.
  • Classes in C# define what an entire "class" of objects can do (what all "players" can do, or what any "car" can do)—what kind of data it stores, and what kinds of things can be done to it, as well as what it can do.
  • You can create a new object with something like the following: Random random = new Random(); This creates a new Random object, which is used to generate random numbers.
  • There's generally no need for destructors in C#, because all objects that are no longer used are garbage collected, like in Java.

Introduction

C# is what people call an "object-oriented" programming language (Sometimes abbreviated "OO", or "OOP"). This is a fancy word for saying all of the code you write will belong in an "object". We'll start this discussion off with defining what an object is, and how they are defined in C# (as a class) and then we'll talk about how to make and use objects in C#.

What is an Object?

Over the many, many years that people have been programming, we've come to learn that one of the best ways to structure code is to copy the way the real world works. In the real world, there are objects. As an example, let's say a player for a game. These objects have certain information about them, like their score, or how many lives they have left. Additionally, in real life, these objects can do certain things on their own, or you can do certain things with them, like take a turn in the game.

In object-oriented programming, we try to copy this structure: there are objects, the objects can have certain data that belongs to it, and there are operations or actions you can do with the object. We've talked about variables already, and we know that this is how C# stores data. We'll see that we can use variables to store data inside of an object, like a player's score. We've also already talked about methods, which we know is a way to do some specific code. We'll also see that we'll use methods to represent the various actions that an object can do, or that can be done to an object, like allowing the player to take a turn.

But before we start talking about how all of this fits together, we need to talk about classes.

What is a Class?

In C#, we will need a way to define what kind of stuff a type of object has, or what it can do. In other words, what can this entire "class" of objects can store in variables, or what can this entire class of stuff do?

In C#, we will create a class, which functions as a sort of template for any object of that type. It will say what an object stores, and what it can do.

To sort of ease into this object/class thing, rather than jumping in and creating our own classes and objects, we'll start with playing around with some of the classes and objects that are a part of the .NET framework, and were made by the people over at Microsoft.

Creating an Object

So let's get started by playing around with the Random class. The Random class is a cool way to let you create random numbers. You can use this class for things like determining the outcome of a die roll, shuffling a deck, or making random events happen.

One thing needs to be said about random number generation. Generally speaking, random numbers on a computer aren't actually random. (Of course, the same thing could be said about people picking random numbers.) They're what they call "pseudo-random".

They are actually chosen using a sophisticated process (what programmers call an "algorithm") that appears random. They need to be sort of "kicked off", starting with a number chosen by the programmer. This number is called a "seed". If you start with the same seed, you will get the same sequence of random numbers, over and over again.

So to prevent this from happening, programmers will often "seed" the random number generator with the current time, accurate to the millisecond. This makes it so that every time you play a game, it happens differently.

The Random class that we're using will automatically seed the random number generation algorithm with the current time, but you can also specify the seed if you would like.

Creating an object is a bit like creating a variable, like we have done before.

To do this, simply use the following:

Random random = new Random();

Here, we start off by specifying our variable's type, like we've done before, only this time, instead of int, or double, or string, we're using the Random type, or the Random class.

We then give our variable a name. In this case, I've called mine random, but you really could have called it anything. (You may see, though, that naming a variable after the type that it is, is fairly common practice.)

Then, like with other variables we use the assignment operator (=) to assign a value to our variable. You'll notice two things that are different about the right hand side of our statement. First, we use the new keyword. This is derived from C++ and Java, that both use similar keywords. This indicates that we're going to create a brand new Random object, using the Random class template. The Random() part might look like a method call to you, and it sort of is. It is a special type of method call that is called a "constructor". We'll see more of this in a second, when we talk about constructors.

At this point, we've got a Random object that we can do stuff with!

Using an Object

Now that we've got a working object, we can do stuff with it. The Random class gives us a few methods that we can call for our new Random object. For starters, there's the Next method. We call this method by using the '.' operator, with the method:

Random random = new Random();
int nextRandomNumber = random.Next();

Of course, you'll probably notice that the Next operator is overloaded, so there are multiple versions of the Next method, including one that lets us choose a range to choose from:

Random random = new Random();
int dieRoll = random.Next(6) + 1; // the Next method returns numbers between 0 and 5, inclusive.

The Random class also has a NextDouble method, which returns numbers between 0 and 1. This allows you to do things like random probabilities and so on.

Using classes and objects allow us to do several things. First, an object keeps track of its own data in its own variables. This means that no one from outside of the class has the ability to mess with its internal data, other than through the ways that the class allows, like by calling a method that makes sure that the way it is being changed is allowed, for instance. The Random class is keeping track of its seed value, somewhere, but on the outside, we don't have access to it, and we shouldn't. It is the responsibility of the Random class, not anyone else's. This, by the way, is called "encapsulation", where an object is in control of its own data, and no one outside of the object can modify it without working with the methods that the object provides.

A second big thing that objects and classes provide is chunks of code that are reusable. We can see that here with the Random class. Someone at Microsoft made the class, and now we can use it whenever we want. That's a really nice feature, because you don't have to create your own Random class, or any other class that has already been made.

Constructors

Every class can define multiple ways of creating an "instance" of an object. An instance of an object is simply a created version of the object, as opposed to referring to the class as a whole. So we already created an instance of the Random class in the examples above.

These ways of creating an object are called constructors. Constructors work a little like a method, with the difference being that they don't return stuff, they just create a new object. (We'll see how this is actually done in the next tutorial.)

Like a method, though, they can have multiple parameters. In the example above, we already saw a constructor with no parameters. Now let's look at another one, that has a parameter.

The Random class just so happens to have a constructor that requires a seed value. To use this constructor instead, you'd simply use this code:

int seed = 5000;
Random random = new Random(seed);

Like I've said, it's all pretty similar to your average method, with the exception of instead of returning a value, we create a new object instead!

Now, before we go on, I want to explain the next three sections of this tutorial. This stuff isn't exactly what I'd call necessary for a crash course about this stuff, but I think it's worth bringing up. If you're getting antsy to make your own classes, you can skip these sections and jump straight ahead to the next tutorial on creating your own classes. On the other hand, if you're not in a rush, I'd recommend at least skimming the next three sections, which cover some useful information about how classes work, behind the scenes.

The Stack vs. the Heap (Extra Information)

When you run your C# program, your computer gives you some memory to put information. This spot is divided into two regions, one called the stack, and one called the heap. Their names come from the way memory is organized inside of them.

The stack is where normal variables are stored (like ints, and doubles). Other information is stored on the stack as well. Whenever you call a method, some information is placed there that allows the computer to keep track of where the program came from before entering the method call, so when you return from the method, it knows where to go back to.

All of the variables inside of a method are placed on the stack as well. For any particular method, all of the information is placed together, in what is called a "frame". So any method call you make puts a new frame on the stack. True to its name, each frame is "stacked" on top of the next in the order it was called.

By the way, if you remember back to the last tutorial, when we talked about recursion and getting a StackOverflowException, this stack is what that error is referring to. It went into so many method calls that it ran out of space on the stack to create another frame.

Objects, on the other hand, are put into the heap. So when you create a new Random object (otherwise known as an instance of the Random class), all of the information that it is keeping track of is put into the heap. There is no real ordering, necessarily, to the heap. It is just put in a big pile—hence the "heap" name.

In order to keep track of your object, a "reference" is used and placed on the stack. So the bulk of the object's information lives in the heap, while you get to keep track of a reference to it on the stack. For those of you with a C/C++ background, a reference is very similar to the pointer concept in either of those two languages. We always keep track of objects using a reference to the object; we'll see why this matters in the next two sections.

For now, though, what's important to know is that "value" types, like your normal variables (int, bool, double, etc.) live on the stack, while "reference" types (objects) live in the heap, with a reference (or many references) to the object staying on the stack.

Passing by Reference and Passing by Value (Extra Information)

Knowing the difference between value and reference types, we can discuss a very important distinction in the way methods use the two.

Say, for instance, that you have a method that looks like this:

static int Multiply(int a, int b)
{
    return a * b;
}

When we call this method from our Main method, or anywhere else, with something like this:

int number1 = 3;
int number2 = 5;
int result = Multiply(number1, number2);

The numbers are "passed in by value", meaning that what gets sent over to the method is just the values of the variable. If we modify the value inside of the method, it only affects the method. So if inside of the Multiply method, we had a statement that says a *= 2;, the value inside of the method would get changed, but back in the method that called it, number1 would not get modified, since all we did is take the value that was stored in it and send it over.

Perhaps this will make even more sense when we contrast it to passing by reference.

Let's say we had a method that required an object as a parameter:

static int RollDie(Random random, int numberOfSides)
{
    int result = random.Next(numberOfSides) + 1;
    return result;
}

This object gets passed by reference, meaning that the method actually is still referring back to the original Random object. Changes to it inside of the method affect the object, even back in the place that called it. This is because the reference to the object is passed over, and the method goes over to the heap to work with it—both places are referring to the exact same object on the heap.

So with objects, which are passed by reference, you need to be aware that changes inside of a method affect the original object, while with value types like int and double, which are passed by value, changes don't affect the original variable.

Garbage Collection: Where Objects Go to Die (Extra Information)

If you're coming from the C++ world, then in addition to constructors, you may be familiar with destructors. Destructors were required to clean up your objects, and free memory on the heap. In virtually all of the code that you will write, manually cleaning up your objects is not required in C#. (If you've never done C++ programming, then it is worth stating that this feature is a freakin' big deal. Cleaning up your objects was always a huge hassle, and if you didn't get it just perfectly, you'd end up leaving objects floating around with no way to access them anymore—a memory leak. C# takes care of all of this for you, allowing you to focus on the real cool stuff.)

C# will automatically clean up the objects that you aren't using any more, in all but a few very rare cases (which we aren't going to run into for a very long while, but involve you directly accessing raw unmanaged resources outside of the .NET Framework). So for now, you can be confident knowing that C# is cleaning up all of your old objects for you.

The process of cleaning up your old data is called "garbage collection". There's a lot going on in garbage collection, but what we care about right now is that when you no longer have a reference to an object anywhere on the stack (and there's no way to get to it from another object that you do have a reference to, like an object with a reference to another object) then C# magically knows that it can get rid of it and use the memory for something else. When it needs more memory, it will go through and clean up anything that it doesn't need any more.

What's Next?

We've now covered the basics of using objects—what they are, and how to create them. And, if you read the Extra Information sections, you should have at least a basic understanding of what's going on behind the scenes with objects.

Our next big step is to create our own classes and objects. It is here that we really get to the core of what C# is.