This worksheet we'll look at how we can store data inside objects and at ways we can use and manipulate that data.
When you've completed this worksheet, you should be able to:
By now, you should be fairly happy with the ideas behind creating a class (worksheet 2), making an object from that class (worksheet 2), and then calling methods belonging to that object (worksheets 4 and 5).
However, if these were the only facilities that objects offered, then there would be little difference between Object Oriented programming and the older, non-OO style of programming. So little difference that we just wouldn't bother with it!
Fortunately, objects offer far more facilities than you've looked at. One hugely important feature is that an object can store some data inside it. (This might seem completely underwhelming but in practice it's extremely important).
Even if you're not a car driver, you'll probably know that most cars have a gear selector lever allowing the driver to operate the car engine at the speed which offers the best power output. To pull away from stationary, the driver selects 1st gear. As the car's speed increases, the driver selects 2nd, 3rd, and finally 4th gear (many cars also have a 5th gear). The driver may also select reverse gear, to make the car travel backwards for parking, etc. There's also a neutral setting, which disconnects the engine from the wheels.
Let's think about how we might create a Car class, modelling the gear change behaviour of a typical car...
Obviously, we need to start with a class, thus:
using System;
using System.Collections.Generic;
using System.Text;
namespace somename
{
class Car
{
}
}
This now begs the question: how do we represent the currently selected gear in C#?
If we use 0 for neutral, -1 for reverse, and 1,2,3,4 for the gear selections then we could
use an int variable. For example: int gear;
But where to put the declaration? So far, we have only put variables inside methods, but our Car class doesn't have any methods (yet). Obviously, you have no choice but to put it directly inside the class. The modified Car class now looks like this:
using System;
using System.Collections.Generic;
using System.Text;
namespace somename
{
class Car
{
int _gear;
}
}
Such a variable, placed inside the class but not inside any particular method has a special name. It's called a field.
Note the common naming convention that a field name in a class should begin with underscore
_
C# does not actually require this, but it can be a very useful thing to do so we'll stick with it.
Let's add a few more fields to the class:
using System;
using System.Collections.Generic;
using System.Text;
namespace somename
{
class Car
{
string _make;
string _model;
int _gear;
}
}
The extra two fields record the make and model of the car. Possible values for these attributes might be "Skoda" "Fabia", or "Ford" "Ka", "Volkswagen" "Polo" or "Posche" "911".
If you look in some text books, they might mark fields public, private or
even protected. For beginners, it doesn't really matter and we suggest that
you don't bother with any of these. Later on, this will become an important issue.
CarTest for
the namespace name.
When a car is being driven in the city, the driver must necessarily change gear at frequent intervals. However, once a car has been manufactured, it never changes its make and model. You don't get into a Skoda Fabia and halfway to college find that it's changed into a Ford Ka.
The make and model are set when the car is manufactured and it would be convenient if C# allowed us to model this. That way, as you create a new car, you could simultaneously state its make and model.
Let's have a look at how we can do this in C#:
Car bobsCar = new Car("Skoda", "Fabia");
Car mikesCar = new Car("Ford", "Ka");
Car jillsCar = new Car("Volkswagen", "Polo");
If you think that the underlined words above look just like a method call, with the make and model as actual parameters, then you'd be right.
There is a special method you can call as you create a new object. This special method is called the constructor.
Every class can have a constructor. The constructor is called whenever an object (of that class) is created and its job is to set the attributes to some initial values. Usually, a constructor uses parameters to obtain those values.
Unlike the other methods you've looked at so far, the name of the constructor must be
the same as the name of the class. Hence the constructor method in the Car class must
be named Car(). You cannot give it any other name!
The constructor can only be called when an object is created and at no other time. In other
words, it can only follow the word new as in the examples given in the previous
section.
The constructor should never be marked void (or any other type).
A well designed constructor should ensure that all the attributes of a class are set to known values. Here is the constructor for the Car class:
public Car(string requiredMake, string requiredModel) {
// Save the car's make and model
_make = requiredMake;
_model = requiredModel;
// Set gear to neutral
_gear = 0;
} // Car
As you can see, the method accepts the required make and model as parameters and simply
copies them to the appropriate attributes. It also sets the gear field to 0 (neutral).
A note on the parameter names: you can see that we chose the names requiredMake and
requiredModel for the parameters to the constructor. These are acceptable enough
choices, but a professional programmer would probably prefer to use the simpler and shorter
names: make and
model. These names are very similar to the actual field names we chose (_make and
_model) except that the field names always begin with an underscore.
Let's see how the various names are related... Field: _make Property: Field Parameter: make
The names look very similar to the eye, but they sufficiently different so that C# doesn't get confused. With a bit of practice we can look at any one of them and immediately identify it as a field, property or parameter! This makes the code easier to write and easier to understand later on.
using System;
using System.Collections.Generic;
using System.Text;
namespace CarTest
{
class Car
{
string _make;
string _model;
int _gear;
public Car(string make, string model)
{
// Save the car's make and model
_make = make;
_model = model
// Set gear to neutral
_gear = 0;
}
}
}
Main() method looks like this:
static void Main(string[] args)
{
Car bobsCar = new Car("Skoda", "Fabia");
Car mikesCar = new Car("Ford", "Ka");
Car jillsCar = new Car("Volkswagen", "Polo");
}
When you ran your CarTest program, it didn't produce any output and so you can't really be sure that the Car class really worked as advertised.
What's needed is another method which prints out the current state of the car - that is, the value of every attribute. Here's a suitable method for the Car class:
public void PrintState()
{
Console.Write(_make);
Console.Write(" ");
Console.Write(_model);
Console.Write(" in gear ");
Console.WriteLine(_gear);
}
When your program calls this method, it prints out the values of the three attributes.
PrintState() method to your Car class and compile it.
Main() method of CarTest so that it now includes a call
to PrintState(), like this:
static void Main(string[] args)
{
Car bobsCar = new Car("Skoda", "Fabia");
Car mikesCar = new Car("Ford", "Ka");
Car jillsCar = new Car("Volkswagen", "Polo");
bobsCar.PrintState();
mikesCar.PrintState();
jillsCar.PrintState();
}
Skoda Fabia in gear 0 Ford Ka in gear 0 Volkswagen Polo in gear 0
When driving, the driver doesn't often think in terms of actual gear numbers, but rather whether it's necessary to go up a gear, or down a gear. We can simulate this by adding two more methods to the class:
GearUp() - to go up a gear. GearDown() - to go down a gear. Here's a very simple form of one of these methods. The other one is very similar.
public void GearUp()
{
_gear++;
}
GearUp() and your version of GearDown() Main() method of CarTest, so it now looks like this:
static void Main(string [] args)
{
Car testCar = new Car("Renault", "Clio");
testCar.gearUp ();
testCar.gearUp ();
testCar.gearUp ();
testCar.gearDown ();
testCar.printState ();
}
GearUp () at least six times in
succession. (Use PrintState() after each gear change).
GearDown() is called a several times in
succession?GearUp() and GearDown() is that they allow
the gear field to be set to inappropriate values, such as 6,-2 or -3.
if statement in GearUp() to prevent the car from going
into a gear higher than 4. Try it out before looking at the answers below.
if statement in GearDown() to prevent the car from going
into a gear lower than 1. Try it out before looking at the answers below.
Within the class, we can use any field as if it were a normal variable; however, what happens
if we try to access a field from outside the class?
Simple. We can't! Access is forbidden and if we try we get an error message when we try to
build the program.
However, we sometimes need to get access to a field from outside, and C# allows this in an extremely elegant manner: the class property
Let's define a property to enable access to the car's make. Remember that _make is
defined as a string field in the Car class:
public string Make
{
get { return _make; }
}
Notes:
public so that it can be accessed from outside the
class.Make)string to match the corresponding
field. get which has no parameters, nor does
it have any ()get method contains a single return statement.Using a property is very simple: just put a dot followed by the property name. For example,
we can obtain the make of bobsCar into a string variable like this:
string bobsMake = bobsCar.Make;and we can print it out directly like this:
Console.WriteLine(bobsCar.Make);
Note that in each case, we always use the property name (Make with a capital M) and not
the field name (_make with a small m).
Make property given above.Main() method of CarTest, so it now looks like
this:
static void Main(string [] args)
{
Car bobsCar = new Car("Skoda", "Fabia");
Car mikesCar = new Car("Ford", "Ka");
Car jillsCar = new Car("Volkswagen", "Polo");
Console.Write("Bob's car is a ");
Console.WriteLine(bobsCar.Make);
Console.Write("Mike's car is a ");
Console.WriteLine(mikesCar.Make);
Console.Write("Jill's car is a ");
Console.WriteLine(jillsCar.Make);
}
Model to the Car class. Main() method to make use of the Model you just
added to Car So far, we've only put a get method into our Properties. This means that an
outside class can obtain a field's value, but can't actually change it. If we want
to allow an outside class to change a field, then we must put a set method in the corresponding
property. Let's try this with a property for the gear field:
public int Gear {
get { return _gear;}
set { _gear = value; }
}
The set method looks very similar to the get method, except that
it assigns a value to the _gear field. The value assigned is represented by
the word value, which is a C# reserved word.
Note that the property name is preceded by the type int which is the type of
the _gear field.
Once the get method is set up, we can directly assign any value to the property.
bobscar.Gear = 4;
Of course, we can stil use the GearUp() and GearDown() methods
which we have previously defined.
Gear
property to the Car class.
Main() method of CarTest.cs, so it now looks like
this:static void main (String [] args) {
Car bobsCar = new Car("Skoda", "Fabia");
Car mikesCar = new Car("Ford", "Ka");
Car jillsCar = new Car("Volkswagen", "Polo");
bobsCar.Gear = 1;
bobsCar.PrintState();
mikesCar.Gear = 2;
mikesCar.GearDown();
mikesCar.PrintState();
jillsCar.Gear = 3;
jillsCar.GearDown();
JillsCar.GearDown();
Console.WriteLine("Jill's car is in gear {0}", jillsCar.Gear);
}
Main() method:jillsCar.Gear = 6;
_gear to 6?We saw in the previous section, that it is possible to assign unrealistic values to the Gear property
(and hence the _gearfield). We put checks (if statements) inside the GearUp() and GearDown() methods,
so perhaps we need to put similar checks inside the set method, like this:
public int Gear {
get { return _gear;}
set
{
_gear = value;
if (_gear < 1) _gear = 1;
if (_gear > 4) _gear = 4;
}
}
The addition of the two if statements now constrains the _gear field
to be in the range 1 to 4. These statements are executed invisibly each time a value is asssigned
to the Gear property. If the programmer attempts to assign a value below 1 then
the first if statement forces the _gear field to 1. Similarly, if
the programmer attempts to assign a value above 4 then the second if statement
forces the _gear field to 4.
Because we've now constrained all our methods we can no longer put a car into neutral
(_gear
= 0) or reverse (_gear = -1).
The obvious solution is to add two more methods: PutInNeutral() and PutInReverse().
These methods are very simple and you should try to write them yourself. Hint: these methods
should write to the _gear field and not to the Gear property. Why?
Gear
property of the Car class to include the new version of set.PutInNeutral() and PutInReverse() into
the Car class.Main() method of CarTest to include tests for the new version of set and the two new methods.
In this worksheet, you performed the following steps:
PrintState() method to see the values of the fields. GearUp() and GearDown() methods to the class to simulate
a gear change up or down. Make and Model, linked the _make and
_ model fields, and each property with a get method.Gear, linked to the _gear field, with get and set methods.if statements to the set method to restrict the _gear field to the range 1 - 4. You have learned the following information about C#:
get and/or a set method to allow communication with other classes.
public void GearUp()
{
if (_gear < 4) {
_gear++;
}
}
public void GearDown()
{
if (_gear > 1) {
_gear--;
}
}