Chapter 1

Showing Some Class

IN THIS CHAPTER

Bullet Introducing and using the C# class

Bullet Assigning and using object references

Bullet Examining classes that contain classes

Bullet Identifying static and instance class members

You can freely declare and use all the intrinsic data types — such as int, double, and bool — to store the information necessary to make your program the best it can be. For some programs, these simple variables are enough. However, most programs need a way to bundle related data into a neat package.

As shown in Book 1, C# provides arrays and other collections for gathering into one structure groups of like-typed variables, such as string or int. A hypothetical college, for example, might track its students by using an array. But a student is much more than just a name — how should this type of program represent a student?

Some programs need to bundle pieces of data that logically belong together but aren't of the same type. A college enrollment application handles students, each of whom has a name, rank (grade-point average), and serial number. Logically, the student’s name may be a string; the grade-point average, a double; and the serial number, a long. That type of program needs a way to bundle these three different types of variables into a single structure named Student. Fortunately, C# provides a structure known as the class for accommodating groupings of unlike-typed variables.

Remember You don't have to type the source code for this chapter manually. In fact, using the downloadable source is a lot easier. You can find the source for this chapter in the CSAIO4D2EBK02CH01 folder of the downloadable source. See the Introduction for details on how to find these source files.

A Quick Overview of Object-Oriented Programming

There are many ways to write applications, and Object-Oriented Programming (OOP) is one of them. You’ve already seen procedural programming techniques in minibook 1, so minibook 2 is your foray into OOP. The purpose of OOP is to make it easier to model the real world using code. So, when you see a Student class, what you see is a model of a real-world student in the form of code. Of course, this model is limited to what you need to do with the student in specific circumstances. For example, the model wouldn't include the student’s eating preferences, unless that’s what you’re modeling. The following sections provide you with a quick overview of OOP that the rest of Book 2 develops further.

Considering OOP basics

OOP relies on the class to create a container for the Student model, so developers call the result the Student class. When you work with Student, your focus is on a real-world student, so you don't care about the underlying code details. The first principle of OOP then is abstraction — the ability to focus on what is needed in the real world, rather than what is needed to program the underlying details. By changing the focus of programming, the development process becomes easier and less error prone.

Keeping data and code together so that everything you need is in one place is another principle of OOP that developers call encapsulation. A class is self-contained in that it has everything needed to model a particular kind of object.

Because of the manner in which classes are put together, after you create a class, you can reuse it for every object that fits within that class’ model. Reuse makes it possible to write a class once and then use it everywhere that the class fits. Many people use the phase Don’t Repeat Yourself (DRY) to emphasize this part of OOP.

Extending classes to meet other needs

An extension of DRY is the idea that you can create a class hierarchy. For example, you could create a class called Vehicle. Vehicles come in many forms, such as Car, Truck, and Bus. Each of these subclasses of Vehicle would inherit (derive and use) the features that Vehicle provides and add specifics of their own with regard to that particular kind of vehicle. The Car class might be further broken down into the Coup, Sedan, and Racer classes. Inheritance makes it possible to start with a general kind of object and then become very specific.

Remember Inheriting characteristics of a parent class may not be enough to fully define a child class. For example, all vehicles have a roof, even if that roof is a temporary structure, as it would be in a convertible. So, you define the Roof property in Vehicle, but further define it in Car, and then provide additional specifics by Car type. This ability to further define classes in subclasses is polymorphism. It's easier to look at polymorphism as saying that one thing is like another thing, but with these changes. People use this form of explanation all the time in the real world, so it shouldn’t be surprising that OOP uses it, too.

Keeping objects safe

Trying to keep the focus on the object and not the underlying code is one reason that OOP classes employ access modifiers, which are indicators of what is and isn’t accessible to users of a class. The use of access modifiers keeps code private so that users don’t worry about implementation details. It also provides the developer with the flexibility to make code changes that don’t modify the class interface.

Technicalstuff The use of access modifiers differs by programming language. In C#, you have these access modifiers:

  • public: The code used to create a particular parent class element is accessible by any other class, even if that class hasn’t inherited from the parent class.
  • protected: The code is accessible by members of the same class and any child classes. So, this code would be accessible by members of the Vehicle class and the Car class, but not accessible by members of the Road class, which doesn't inherit from the Vehicle class.
  • private: The code is accessible only by members of the same class. For example, the code is accessible by any member of the Vehicle class, but not accessible by members of the Car class.
  • internal: This is the super-secret code that is accessible only from within a given assembly, but not within any other assembly. It means that you can create code that is only accessible from your application and not any other application, even if the other application uses classes from your application.

Tip You can also find odd combinations of the four basic access modifiers in use such as protected internal. This book doesn't cover the odd assorted access modifier combinations, but you can read about them online in places like https://www.c-sharpcorner.com/uploadfile/puranindia/what-are-access-modifiers-in-C-Sharp/.

Working with objects

Classes represent blueprints for real-world objects. When you create a Student class, what you really have is a blueprint for creating a student object. To create an object, you instantiate the class. Perhaps you might create the Mike and Sally objects, both of which are instances of the Student class. Both objects would have the same structure, but the details would differ. For example, one object would have a Name value of "Mike" and the other a Name value of "Sally".

Remember This idea of objects being instances of classes brings up the final consideration for this section, which is the structure of a class and therefore an object. A class contains these elements:

  • Methods: Defines what you can do with the object, such as changing a name or a grade-point average. Methods make it possible to interact with objects in a consistent manner.
  • Properties: Determines the characteristics of the object, such as name or grade-point average. Properties ensure that every object has the same characteristics, even though the specifics of those characteristics differ between objects. Also, properties make data types easier to deal with. Instead of thinking about a string data type for a student's identifier, you think about a Name property.
  • Events: Alerts anyone who is listening for the event that something has changed within the object. Perhaps someone has spoken to the Student or changed their grade-point average. Events make it possible to monitor objects.

Defining a Class and an Object

A class is a bundling of unlike data and functions that logically belong together into one tidy package. C# gives you the freedom to foul up your classes any way you want, but good classes are designed to represent concepts.

Computer science models the world via structures that represent concepts or things in the world, such as bank accounts, tic-tac-toe games, customers, game boards, documents, and products. Analysts say that “a class maps concepts from the problem into the program.” For example, your problem might be to build a traffic simulator that models traffic patterns for the purpose of building streets, intersections, and highways.

Any description of a problem concerning traffic would include the term vehicle in its solution. Vehicles have a top speed that must be figured into the equation. They also have a weight, and some of them are clunkers. In addition, vehicles stop and vehicles go. Thus, as a concept, vehicle is part of the problem domain.

A good C# traffic-simulator program would necessarily include the class Vehicle, which describes the relevant properties of a vehicle. The C# Vehicle class would have properties such as topSpeed, weight, and isClunker.

Because the class is central to C# programming, the rest of minibook 2 discusses the ins and outs of classes in much more detail. This chapter gets you started.

Defining a class

This section begins with a class declaration for the VehicleData example. An example of the class Vehicle may appear this way (you put this class definition before class Program in the file):

public class Vehicle
{
public string model { get; set; } // Name of the model
public string manufacturer { get; set; } // Name of the manufacturer
public int numOfDoors { get; set; } // Number of vehicle doors
public int numOfWheels { get; set; } // You get the idea.
}

A class definition begins with the words public (the access modifier) and class (the kind of structure you're creating), followed by the name of the class — in this case, Vehicle. Like all names in C#, the name of the class is case sensitive. C# doesn’t enforce any rules concerning class names, but an unofficial rule holds that the name of a class starts with a capital letter.

The class name is followed by a pair of open and closed braces. Within the braces, you have zero or more members. The members of a class are items that make up the parts of the class. In this example, class Vehicle starts with the member model of type string, which contains the name of the model of the vehicle. If the vehicle were a car, its model name could be Trooper II. The second member of this Vehicle class example is manufacturer of type string. The other two properties are the number of doors and the number of wheels on the vehicle, both of which are type int.

Tip As with any variable, make the names of the members as descriptive as possible. A good variable name usually says it all. However, adding comments, as shown in this example, can make the purpose and usage of the members clearer.

The public modifier in front of the class name makes the class universally accessible throughout the program. Similarly, the public modifier in front of the member names makes them accessible to everything else in the program. Other modifiers are possible. (Chapter 4 in this minibook covers the topic of accessibility in more detail and shows how you can hide some members.)

The class definition should describe the properties of the object that are salient to the problem at hand. That's a little hard to do right now because you don’t know what the problem is, but it becomes clearer as you work through the problem.

What’s the object?

Defining a Vehicle design isn’t the same task as building a car. Someone has to cut some sheet metal and turn some bolts before anyone can drive an actual car. A class object is declared in a similar (but not identical) fashion to declaring an intrinsic object such as an int.

Remember The term object is used universally to mean a “thing.” Okay, that isn't helpful. An int variable is an int object. A car is a Vehicle object. The following code segment creates a car of class Vehicle:

Vehicle myCar;
myCar = new Vehicle();

The first line declares a variable myCar of type Vehicle, just as you can declare a somethingOrOther of class int. (Yes, a class is a type, and all C# objects are defined as classes.) The new Vehicle() call instantiates (creates) a specific object of type Vehicle (the blueprint or class) and stores the location in memory of that object into the variable myCar (the instance). The new keyword has nothing to do with the age of myCar. (My car could qualify for an antique license plate if it weren't so ugly.) The new operator creates a new block of memory in which your program can store the properties of myCar.

Technicalstuff The intrinsic num and the object myCar are stored differently in memory. The first uses the stack (linear memory that is part of the program itself) and the second uses the heap (hierarchical memory that is taken from the system). (If you want a really detailed description of the difference, check out the article at https://www.guru99.com/stack-vs-heap.html.) The variable num actually contains the value 1 (as an example) rather than a memory location. The new Vehicle() expression allocates the memory necessary on the heap. The variable myCar contains a memory reference (a pointer to the memory) rather than the actual values used to describe a Vehicle.

Accessing the Members of an Object

Each object of class Vehicle has its own set of members. The following expression stores the number 1 into the numberOfDoors member of the object referenced by myCar:

myCar.numberOfDoors = 1;

Technicalstuff Every C# operation must be evaluated by type as well as by value. The object myCar is an object of type Vehicle. The variable Vehicle.numberOfDoors is of type int. (Look again at the definition of the Vehicle class.) The constant 5 is also of type int, so the type of the variable on the right side of the assignment operator matches the type of the variable on the left. Similarly, the following code stores a reference to the strings describing the model and manufacturer name of myCar:

myCar.manufacturer = "BMW"; // Don't get your hopes up.
myCar.model = "Isetta"; // The Urkel-mobile

The Isetta was a small car built during the 1950s with a single door that opened the entire front of the car. Check it out at https://www.motortrend.com/vehicle-genres/1956-1962-bmw-isetta-300-collectible-classic/.

Working with Object-Based Code

The “Defining a class” section of this chapter tells you how to declare a class to use within an application. Starting with C# 9.0, you have a number of ways to interact with this class. The following sections show two of these techniques. The traditional approach in the first section that follows uses the Vehicle class in a manner that works with older versions of C#, and you should use it for compatibility purposes with existing code that uses the same approach. It requires 52 lines of code to get the job done. The C# 9.0 approach in the second section that follows is easier to read and uses only 47 lines of code. That's not much of a difference until you start looking at the number of lines saved in a larger program.

Using the traditional approach

The simple VehicleData program performs these tasks:

  • Define the class Vehicle.
  • Create an object myCar.
  • Assign properties to myCar.
  • Retrieve those values from the object for display.

Here's the code for the VehicleData program:

static void Main(string[] args)
{
// Prompt user to enter a name.
Console.WriteLine("Enter the properties of your vehicle");

// Create an instance of Vehicle.
Vehicle myCar = new Vehicle();

// Populate a data member via a temporary variable.
Console.Write("Model name = ");
string s = Console.ReadLine();
myCar.model = s;

// Or you can populate the data member directly.
Console.Write("Manufacturer name = ");
myCar.manufacturer = Console.ReadLine();

// Enter the remainder of the data.
// A temp variable, s, is useful for reading ints.
Console.Write("Number of doors = ");
s = Console.ReadLine();
myCar.numOfDoors = Convert.ToInt32(s);
Console.Write("Number of wheels = ");
s = Console.ReadLine();
myCar.numOfWheels = Convert.ToInt32(s);

// Now display the results.
Console.WriteLine(" Your vehicle is a ");
Console.WriteLine(myCar.manufacturer + " " + myCar.model);
Console.WriteLine("with " + myCar.numOfDoors + " doors, "
+ "riding on " + myCar.numOfWheels
+ " wheels.");

Console.Read();
}

The program creates an object myCar of class Vehicle and then populates each field by reading the appropriate data from the keyboard. (The input data isn't — but should be — checked for legality.) The program then writes myCar’s properties in a slightly different format. Here’s some example output from executing this program:

Enter the properties of your vehicle
Model name = Metropolitan
Manufacturer name = Nash
Number of doors = 2
Number of wheels = 4

Your vehicle is a
Nash Metropolitan
with 2 doors, riding on 4 wheels

Tip The calls to Write() as opposed to WriteLine() leave the cursor directly after the output string, which makes the user's input appear on the same line as the prompt. In addition, inserting the newline character ' ' in a write generates a blank line without the need to execute WriteLine() separately.

Using the C# 9.0 approach

The C# 9.0 approach to this example appears in the VehicleData2 example. The Main() method is different in arrangement, as shown here.

static void Main(string[] args)
{
// Prompt user to enter a name.
Console.WriteLine("Enter the properties of your vehicle");

// Obtain the data needed to create myCar.
Console.Write("Model name = ");
string s = Console.ReadLine();

Console.Write("Manufacturer name = ");
string mfg = Console.ReadLine();

Console.Write("Number of doors = ");
int doors = Convert.ToInt32(Console.ReadLine());

Console.Write("Number of wheels = ");
int wheels = Convert.ToInt32(Console.ReadLine());

// Create an instance of Vehicle.
Vehicle myCar = new()
{
model = s,
manufacturer = mfg,
numOfDoors = doors,
numOfWheels = wheels
};

// Now display the results.
Console.WriteLine($" Your vehicle is a {myCar.manufacturer} " +
$"{myCar.model} with {myCar.numOfDoors} doors riding on " +
$"{myCar.numOfWheels} wheels");

Console.Read();
}

The example is much more structured because it collects all of the data needed to create myCar first. It then instantiates the myCar object using Vehicle with two changes. First, you don't declare the object type when calling new(). The compiler deduces the object type based on the type you provide for the object. Second, you set the properties individually, which means that you set them as part of a list after new(). Using this approach is significantly clearer because you know precisely where the values in myCar come from and have to look in only one place to see them. The output of this version is the same as before.

Discriminating between Objects

Detroit car manufacturers can track every car they make without getting the cars confused. Similarly, a program can create numerous objects of the same class, as shown in this example:

Vehicle car1 = new() {manufacturer = "Studebaker", model = "Avanti"};

// The following has no effect on car1.
Vehicle car2 = new() {manufacturer = "Hudson", model = "Hornet"};

Creating an object car2 and assigning it the manufacturer name Hudson has no effect on the car1 object (with the manufacturer name Studebaker). That's because car1 and car2 appear in totally separate memory locations. In part, the ability to discriminate between objects is the real power of the class construct. The object associated with the Hudson Hornet can be created, manipulated, and dispensed with as a single entity, separate from other objects, including the Avanti. (Both are classic automobiles, especially the latter.)

Can You Give Me References?

The dot operator and the assignment operator are the only two operators defined on reference types:

// Create a null reference.
Vehicle yourCar;

// Assign the reference a value.
yourCar = new Vehicle();

// Use dot to access a member.
yourCar.manufacturer = "Rambler";

// Create a new reference and point it to the same object.
Vehicle yourSpousalCar = yourCar;

The first line creates an object yourCar without assigning it a value. A reference that hasn't been initialized is said to point to the null object. Any attempt to use an uninitialized (null) reference generates an immediate error that terminates the program.

Technicalstuff The C# compiler can catch most attempts to use an uninitialized reference and generate a warning at build-time. If you somehow slip one past the compiler, accessing an uninitialized reference terminates the program immediately.

The second statement creates a new Vehicle object and assigns it to yourCar. The last statement in this code snippet assigns the reference yourSpousalCar to the reference yourCar. This action causes yourSpousalCar to refer to the same object as yourCar. This relationship is shown in Figure 1-1.

Snapshot of two references to the same object.

FIGURE 1-1: Two references to the same object.

The following two calls that set the car's model in the following code have the same effect:

// Build your car.
Vehicle yourCar = new Vehicle();
yourCar.model = "Kaiser";

// It also belongs to your spouse.
Vehicle yourSpousalCar = yourCar;

// Changing one changes the other.
yourSpousalCar.model = "Henry J";
Console.WriteLine("Your car is a " + yourCar.model);

Executing this program would output Henry J and not Kaiser. Notice that yourSpousalCar doesn't point to yourCar; rather, both yourCar and yourSpousalCar refer to the same vehicle (the same memory location). In addition, the reference yourSpousalCar would still be valid, even if the variable yourCar were somehow “lost” (if it went out of scope, for example), as shown in this chunk of code:

// Build your car.
Vehicle yourCar = new Vehicle();
yourCar.model = "Kaiser";

// It also belongs to your spouse.
Vehicle yourSpousalCar = yourCar;

// When your spouse takes your car away …
yourCar = null; // yourCar now references the "null object."

// …yourSpousalCar still references the same vehicle
Console.WriteLine("your car was a " + yourSpousalCar.model);

Executing this program generates the output Your car was a Kaiser, even though the reference yourCar is no longer valid. The object is no longer reachable from the reference yourCar because yourCar no longer contains a reference to the required memory location. The object doesn't become completely unreachable until both yourCar and yourSpousalCar are “lost” or nulled out.

At some point, the C# garbage collector steps in and returns the space formerly used by that particular Vehicle object to the pool of space available for allocating more Vehicles (or Students, for that matter). The garbage collector only runs after all the references to an object are lost. (The “Garbage Collection and the C# Destructor” sidebar at the end of Chapter 5 of this minibook says more about garbage collection.)

Tip Making one object variable (a variable of a reference type, such as Vehicle or Student, rather than one of a simple type such as int or double) point to a different object — as shown here — makes storing and manipulating reference objects in arrays and collections quite efficient. Each element of the array stores a reference to an object, and when you swap elements within the array, you're just moving references, not the objects themselves. References have a fixed size in memory, unlike the objects they refer to.

Classes That Contain Classes Are the Happiest Classes in the World

The members of a class can themselves be references to other classes, as shown in the VehicleData3 example, which uses the VehicleData2 example as a starting point. For example, vehicles have motors, which have power and efficiency factors, including displacement. You could throw these factors directly into the class this way:

public class Vehicle
{
public string model; // Name of the model
public string manufacturer; // Ditto
public int numOfDoors; // The number of doors on the vehicle
public int numOfWheels; // You get the idea.

// New stuff:
public int power; // Power of the motor [horsepower]
public double displacement; // Engine displacement [liter]
}

However, power and engine displacement aren't properties of the car. For example, your friend’s Jeep might be supplied with two different motor options that have drastically different levels of horsepower. The 2.4-liter Jeep is a snail, and the same car outfitted with the 4.0-liter engine is quite peppy. The motor is a concept of its own and deserves its own class:

public class Motor
{
public int power; // Power [horsepower]
public double displacement; // Engine displacement [liter]
}

You can combine this class into the Vehicle (see boldfaced text):

public class Vehicle
{
public string model { get; set; } // Name of the model
public string manufacturer { get; set; } // Name of the manufacturer
public int numOfDoors { get; set; } // Number of vehicle doors
public int numOfWheels { get; set; } // You get the idea.
public Motor motor { get; set; } // Type of engine.
}

Creating myCar now appears this way (instead of asking for input, this version simply provides the required values in the interest of space):

static void Main(string[] args)
{
// Create an instance of Vehicle.
Vehicle myCar = new()
{
model = "Cherokee Sport",
manufacturer = "Jeep",
numOfDoors = 2,
numOfWheels = 4,
motor = new()
{
power = 230,
displacement = 4.0
}
};

// Now display the results.
Console.WriteLine($" Your vehicle is a {myCar.manufacturer} " +
$"{myCar.model} with {myCar.numOfDoors} doors riding on " +
$"{myCar.numOfWheels} wheels using a {myCar.motor.displacement}" +
$" liter engine producing {myCar.motor.power} hp.");

Console.Read();
}

Notice how you can place new() statements within new() statements to define an entire hierarchy in a manner that is very easy to follow. Earlier versions of C# would require that you instantiate motor first and then add it to myCar. Everything is self-contained within a single hierarchical declaration now so that the opportunities for errors are fewer. Notice that you access the power and displacement values using a dot hierarchy as well: myCar.motor.power and myCar.motor.displacement.

Generating Static in Class Members

Most data members of a class are specific to their containing object, not to any other objects. Consider the Car class:

public class Car
{
public string licensePlate; // The license plate ID
}

Because the license plate ID is an object property, it describes each object of class Car uniquely. For example, your spouse's car will have a different license plate from your car, as shown here:

Car spouseCar = new Car();
spouseCar.licensePlate = "XYZ123";

Car yourCar = new Car();
yourCar.licensePlate = "ABC789";

However, some properties exist that all cars share. For example, the number of cars built is a property of the class Car but not of any single object. These class properties are flagged in C# with the keyword static:

public class Car
{
public static int numberOfCars; // The number of cars built
public string licensePlate; // The license plate ID
}

Remember Static members aren't accessed through the object. Instead, you access them by way of the class itself, as this code snippet demonstrates:

// Create a new object of class Car.
Car newCar = new Car();
newCar.licensePlate = "ABC123";

// Now increment the count of cars to reflect the new one.
Car.numberOfCars++;

The object member newCar.licensePlate is accessed through the object newCar, and the class (static) member Car.numberOfCars is accessed through the class Car. All Cars share the same numberOfCars member, so each car contains exactly the same value as all other cars.

Remember Class members are static members. Non-static members are specific to each “instance” (each individual object) and are instance members. The italicized phrases you see here are the generic way to refer to these types of members.

Defining const and readonly Data Members

One special type of static member is the const data member, which represents a constant. You must establish the value of a const variable in the declaration, and you cannot change it anywhere within the program, as shown here:

class Program
{
// Number of days in the year (including leap day)
public const int daysInYear = 366; // Must have initializer.

public static void Main(string[] args)
{
// This is an array, covered later in this chapter.
int[] maxTemperatures = new int[daysInYear];
for(int index = 0; index < daysInYear; index++)
{
// …accumulate the maximum temperature for each
// day of the year …
}
}
}

You can use the constant daysInYear in place of the value 366 anywhere within your program. The const variable is useful because it can replace a mysterious number such as 366 with the descriptive name daysInYear to enhance the readability of your program. C# provides another way to declare constants — you can preface a variable declaration with the readonly modifier, like so:

public readonly int daysInYear = 366; // This could also be static.

As with const, after you assign the initial value, it can't be changed. Although the reasons are too technical for this book, the readonly approach to declaring constants is usually preferable to const.

You can use const with class data members like those you might have seen in this chapter and inside class methods. But readonly isn't allowed in a method. Chapter 2 of this minibook dives into methods.

An alternative convention also exists for naming constants. Rather than name them like variables (as in daysInYear), many programmers prefer to use uppercase letters separated by underscores, as in DAYS_IN_YEAR. This convention separates constants clearly from ordinary read-write variables.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset