Coding With Flavio
Lesson Summary:
Classes
A class is an object with defined behaviors. We have used multiple classes thus far, from the "Program" class that wraps our Main method down to classes we've used for our code, such as strings and Lists. We can also create our own classes, which allows us to have a brand new type, created and defined by ourselves. For example, just like there is a type int to define an integer, we can create a class "Car", which becomes a new object type to represent a car, whose behavior we can define ourselves (as we show in the video).
​
If we were to create a class for Car, we can then specify behaviors such as traveling, counting mileage, running out of gas, etc.
​
An example of a class definition for a Car:
​
class Car
{
private int distance = 0;
public void GoForward()
{
distance++;
}
}
​
-
We define a new car by specifying class followed by the name of our class, shown in tan here as Car. This can be anything you choose, but make sure it's recognizeable.
-
The contents of your class are defined between the braces {} following class Car.
-
In red we have a class variable which will keep track of the distance this car has travelled. Here it starts at 0. When we create a new car (more on this later), this value will start at 0. A variable defined as such within a class is called a field.
-
In blue we have a method of this class. This is like any method or function we have seen before, with one small difference that I will mention later. Calling this method will increment by one the value of our field "distance". This function returns no value (thus void) and has no parameters (thus ()) but methods in classes can return values and have parameters as well.
-
In purple we have 2 special identifiers, public and private. They specify whether or not the field and the method can be accessed outside of the class definition. I will describe these in more detail soon.
-
Fields and methods that belong to a class, such as distance and GoForward, are called members of the class.
​
What we show above is the definition of our class Car. This is like a schematic. We have not yet created a car; we only defined what a car looks like and acts like. To create a car, we do the following in our code, such as in Main:
​
static void Main(string[] args)
{
Car myCar = new Car();
}
​
Here, we created what we call an instance of the Car class as a variable called myCar of type Car. Note that we use the new operator, and that we include empty parentheses () at the end (more on why we do this on the addendum for this lesson). This car's distance field has a value of 0 right now. We can call the class methods as follows:
​
Car myCar = new Car();
myCar.GoForward();
Note that the period operator (.) is used to access the GoForward() method. We have seen this many times in the past, such as with list.Add() for Lists.
Due to the keyword public, we can call GoForward() from the code above. However, the keyword private for the field distance makes it so that we cannot access or modify that field anywhere outside of the definition of the class Car. We can see an example of where it is possible to access a private field by looking at the definition of Car, inside the GoForward method, where we do distance++. However, this field is not accessible anywhere outside the braces {} of the class Car. Note that methods can be private also.
​
We can have multiple instances of car:
​
Car car1 = new Car();
Car car2 = new Car();
car1.GoForward();
car1.GoForward();
​
In this example, car1 will have a distance with value 2, while car2 will have a distance with value 0. Notice that the distance field is specific to each instance of the class Car, and that value is not shared. Thus calling GoForward() on car1 will only change the value of distance for car1. The field distance for car2 will remain at 0 until we call the following:
​
car2.GoForward();
​
​
Encapsulation
The concept of hiding data using keywords such as private, like we did for distance in the example above, is called encapsulation. The idea behind encapsulation is that the encapsulated components should only be known and manipulated by the class itself. In the example above, we made it so that distance can only be modified by using GoForward(), in which case it gets incremented by exactly one. In contrast, we could change the field distance to be public as by doing the following within class Car:
​
public int distance = 0;
​
If we do this, we can now do the following:
​
Car myCar = new Car();
myCar.distance++;
​
In fact, we can also do this:
Car myCar = new Car();
myCar.distance = 5;
​
While this kind of thing is sometimes ok to do, it's often preferable to keep certain fields like this one encapsulated. Here is an example. Imagine we want to keep track of gas. We start with 5 units of gas. Let's define the class as such:
​
class Car
{
private int distance = 0;
private int gas = 5;
public void GoForward()
{
if (gas > 0)
{
distance++;
gas--;
}
}
}
​
In our revised GoForward method, we only move forward if we have sufficient gas, and when we do, we decrement gas by 1 by doing gas--. If we don't have enough gas, we don't change the distance.
​
If distance is not marked private in this case, then someone could do this:
​
Car myCar = new Car();
myCar.distance++;
​
However, this neither reduces the amount of gas, nor does it check whether or not there is sufficient gas to change the distance. In this case, the Car class would be improperly designed becase the person who coded it left it open for improper use; since I see that I can modify distance, I choose to do so, at the expense of the proper functionality of the gas feature.
​
Another example you may see of this is with List. You may notice that you cannot set the value of the property Count directly. This property is "read only" because changing the count would make little sense without also modifying the contents of the list, thus putting the list object in an invalid state.
​
Encapsulation may seem a bit odd at first, but it's extremely important.
Abstraction
The general idea behind abstraction is that your classes should be designed to be easy to use and understand. More specifically, they should hide layers of complexity from the user if those layers are not directly necessary in order to use the class. Think of those things as gears and cogs; when you see a clock, you see the clock's face, but you do not see the gears and cogs, despite the fact they are essential to make the clock work.
​
As an example, I mentioned in the chapter on Lists that a C# List is built on top of an array. However, you cannot directly access that array when working with a list. Instead, that component is abstracted from you, and all you can access are the helpful methods like Add, Remove, etc.. The class thus only makes public those components which are immediately necessary to use the List as intended, no more, no less.
​
For our Car example, we abstract away the functionality of fuel and distance by simply providing a single way of interacting with the car: GoForward. The Car class itself specifies how the car consumes gas, moves forward, or stalls in the absence of gas; these details are hidden from the user of the class, as they do not need to see the gears and cogs.
Addendum: Constructors and Properties
Two important components of classes: one helps you access values, the other helps you initialize them.
Addendum Summary:
Constructors
A constructor is a specialized method-like member of a class which lets you define some behavior for the moment an instance of a class is created (such as using the operator new). A constructor is defined similar to a method, but with no return type specified, and with its name matching the name of the class. For example, a constructor for the class Car can be as follows:
​
class Car
{
// This is the constructor for Car.
// Its name must match the name of the class, and have no return type.
public Car()
{
}
}
​
Notice that we mark it public. Though there are many exceptions, constructors tend to be public. To use this constructor, we use the following code:
​
Car myCar = new Car();
​
The constructor defined above is invoked as a result of calling "new Car()". Note that the empty parentheses () need to be specified to match the empty parameter list in the definition. Like methods, constructors can optionally allow parameters, which are then passed in during the time the instance is created. In the example above, we do not pass in any parameters, similar to methods like ReadLine(). However, we can define a different constructor which takes a parameter:
​
class Car
{
private int id;
public Car(int initialId)
{
id = initialId;
}
}
​
We can use this as such:
​
Car myCar = new Car(12345);
​
This will initialize the private field "id" to the value 12345. One important thing to note is that the first constructor we mentioned (which takes no parameters and has nothing defined in its body) is exists implicitly as long as no constructor is specified. This is called the default or empty consturcotr, and it is the reason we can use "new Car()" without specifying any constructor. However, as soon as we create a different constructor (such as the one which takes an id), that constructor will replace the default one, making it no longer implicitly defined. That means that after the latest example (with the int parameter), we can no longer do the following:
​
Car myCar = new Car(); // No longer allowed because default constructor was replaced
​
This is a good way to force the programmer to include an ID when creating an instance of Car, and is a sound design strategy for ensuring the existence of required fields. However, one may want to still allow for an empty constructor, as it may just define a behavior like creating a random id. Just like we can overload methods (see lesson 5 addendum), we can also overload constructors. The following will allow both an empty constructor and a constructor with an id argument:
​
class Car
{
private int id;
public Car(int initialId)
{
id = initialId;
}
public Car()
{
id = 0;
}
}
​
With this, both of these become valid:
​
Car myCar = new Car();
Car myCar = new Car(12345);
Properties
A property is basically a simple way of specifying the following two methods of this class:
​
class MyClass
{
private int myValue;
public int GetValue()
{
return this.myValue;
}
public void SetValue(int value)
{
this.myValue = value;
}
}
​
We often refer to these methods which primarily get and set data as "accessors", with the GetValue being a "getter" and the SetValue being a "setter". Properties allow us to easily specify getters and setters like the ones we have above:
​
class MyClass
{
private int myValue;
public int Value
{
get
{
return this.myValue;
}
set
{
this.myValue = value;
}
}
}
​
In this example, "Value" is our property. Properties often rely on getting or setting a field, such as myValue in the above example; these fields which are tied to properties are often called backing fields. Note that it uses special keywords get and set to define the getter and setter, respectively. Also note that "value" within the set block is a special keyword; within the context of set, "value" refers to the value we are setting the property to. This makes more sense when seeing an example. The following shows how to use this property:
​
MyClass thing = new MyClass();
thing.Value = 5; // This calls the set for Value. The keyword "value" in there is replaced with 5.
int num = thing.Value; // This calls the get for Value. It returns the field myValue.
​
Contrast this with using the methods from before:
​
MyClass thing = new MyClass();
thing.SetValue(5);
int num = thing.GetValue();
​
They are functionally similar, but many would argue that the property is more concise.
​
Also note that we can define a property as read-only by removing the setter (or making it private):
​
class MyClass
{
private int myValue;
public int Value
{
get
{
return this.myValue;
}
}
}
​
Now we cannot do the following:
​
MyClass thing = new MyClass();
thing.Value = 5; // Compiler will complain that Value is read-only.
​
This is a good way to ensure a field can be read but not modified, which is useful for properties like the List's Count, which provides useful information about the state of the object but would not make sense to change directly outside of the class. Furthermore, read-only properties are quite useful when used with constructors to initialize values.
​
Additional Tips:
Auto-Properties
There is a special way in which you can define a property which is referred to as an "auto-property". Take the following example:
​
class Car
{
public int MyProperty { get; set; }
}
​
In the example above, MyProperty is a property which behaves essentially like the following public field:
​
class Car
{
public int myField;
}
​
In both cases, I can freely get and set the values of the property/field. Here is an example of using the auto propery:
​
Car myCar = new Car();
myCar.MyProperty = 5;
int num = myCar.MyProperty;
​
As you see, the auto-property defines both a setter and a getter and behaves much like a public field. In fact, this is exactly what happens; when the code is compiled, auto-properties create a field and a getter and setter for that field behind the scenes. This is just a convenience feature. The compiler basically turns the auto-property into something similar to this:
​
class Car
{
private int myPropery;
public int MyProperty
{
get { return this.myPropery; }
set { this.myPropery = value; }
}
}
​
Why use this instead of a field? Many of the reasons are stylistic; maybe your class has many properties and you want to add some value that can be publicly get/set directly, such as Name. Often it's good to keep your public data consistent, such as by preferring properties over fields. Some people prefer to never have public fields, marking them all as private. In such cases, having public properties like this is an acceptable way to be consistent with that idea. You will frequently see this practice in .NET classes, where public fields are less common than public properties that fit the same function.
​
However, one non-stylistic reason to prefer auto-properties over fields is that many frameworks such as .NET actually treat properties and fields differently. One important example of this is in serialization, a concept that I will cover much later (probably in the Advanced C# series). For now, it's just good to know that this option exists, and we may use it going forward.
Practice: Animal Brigade
Different animals make different sounds. We can prove this fact of life using classes.