Introduction & Overview
Object Orientation & C# Classes
The course is part of this learning path
This course takes code from the Data Types and Variables course and refines it using object-oriented (OO) principles. We explore some of the main concepts of OO programming during this process, such as encapsulation, code reuse, and inheritance.
Along the way, we learn more about essential code structures such as conditional evaluation with if-then-else statements, functions for grouping code that performs a specific task, and for-loops for dynamically repeating an action. We will also look at how .NET and C# have object-oriented baked-in as a fundamental design principle underpinning the framework and language.
- Understand the benefits of object orientation and what came before it
- Learn about essential code structures and turn code into a class
- Refine the class code and learn more object-oriented concepts
- Learn about inheritance, a fundamental object-oriented concept
- Understand how object-orientation is a foundation principle of C# and .NET
This course is intended for those who already have an understanding of data types and variables in C# and now want to learn about object-oriented principles.
To get the most out of this course, you should have an understanding of C# as well as basic data types: strings, numbers, and Booleans. In order to follow along with the demos, you should also have a working development environment, whether in Windows, Linux, or macOS.
The GitHub repository for this course can be found here.
Having a class defined in the main program file is not the done thing. It is usual to have one class per file. However, this is not a hard and fast rule but more of a convention that promotes readability. I'll add another file to the project called boards.cs, add the using system statement at the top, and include the file in the checkerboard namespace. Next, I'll move the board class into the boards file and split the screen to see program and boards side-by-side. If I change the checkerboard namespace to boards, the board class is no longer recognized within the checkerboard namespace of the program file.
There are two ways to address this. One is to use a fully qualified name, meaning you need to prefix the class name with the namespace name, so that's boards.board. Alternately, use a using statement at the top of the file, like you would if including additional .NET libraries, so that would be using boards. This is an artificial situation, as you usually wouldn't have multiple namespaces in such a small program. Typically, but again not a hard and fast rule; you usually have one namespace per application or library. Earlier, I talked about having two methods with the same name but different parameters, and now I'm going to add a default constructor to the class with no parameters. Multiple methods with the same name is called method overloading.
Because we are not setting the rows or columns in this constructor, I will initialize both of those private members to 0. If I create a board with this constructor, it will have no dimensions. I need another way to set the rows and column sizes. We can do this using publicly declared variables which in the context of classes are called properties. You can create a property by typing prop and then hitting the tab key. This will create a boilerplate integer property. Here, I have a public rows property different from the private rows member as C# variable names are case sensitive. If I assign a value to the public rows, that will have no effect as the private rows is being used in all the class methods. I'll use the getter and setter to assign the public rows to the private rows. In effect, the public rows property is like a gateway or a door to the private or internal rows member, so the get says return the value of rows, and the set says make little rows the value of capital rows. How I have the rows property set up here is how you used to have to do it. As C# has evolved, the boilerplate example with just get and set has become shorthand for this. Of course, this means that you would not have a private rows member mapped to the public one.
I'll do the same for columns. To use the default constructor with no parameters, I'll need to make the Setup method public - this will become apparent in a moment. Back in program.cs, I'll change to the default constructor, assign values to the board's rows and columns properties, and call the public Setup method. You may have spotted an issue with this change. The checkers array is created only in the original constructor, so calling Setup with the default constructor will cause an error when assigning values to checkers elements that don't exist. Moving the checkers creation to the setup method will fix this. Ok, let's save and run, and that is the result we expect.
What happens if I make columns five, so not a square board, but even more problematic, a board with an odd number of columns. You end up with, well, not a checkerboard pattern. Hmmm, the Debug Console is saying that we've got six lines like this. It's collapsing the repeated output. We can change this default VS Code behavior by going into preferences under code and then settings. In Windows, preferences are under the file menu. Once in settings, click on debug under features and uncheck collapse identical lines. Settings are saved automatically, and when I run that again, we get the expected output.
We could implement some logic to cater to the case of the odd column, but let's just prevent the user from specifying odd-numbered columns in the first place. One of the very cool features of public property's getters and setters is that you can use a function to get or set a variable. I'll replace columns equals value with a call to a new method called SetColumns. SetColumns will be private as only the board class needs to know about it, and I'll check the value being assigned to the columns property to make sure that it is an even number. If it isn't even, I'll display a message. To work out if a value is odd or even, I will use the modulo operator, a percent sign, which says divide value by two and tell me the remainder. If the remainder is one, then the value is an odd number, and I'll display a message to that effect. We have seen the if statement before, but this time I will add an else component, assigning value to columns. Because the default constructor initializes columns to zero, and if columns is odd it will remain zero, then I don't want to bother populating the checkers array. If columns are zero, then I don't want to bother creating the checkers array, so I'll wrap it in if columns are greater than zero. When an object is declared but not instantiated, i.e., created, the variable representing that object is in an unknown state called null. Accessing or trying to access null objects is one of the most common runtime errors in software. Logic will tell us that if columns equal zero, then the inner loop of the print method will never execute, as c is zero and zero is not less than zero.
While we won't get an error of accessing a null object, we will have rows number of blank lines printed by Console.WriteLine. To avoid the situation, I will wrap all the print code in an if statement verifying that checkers does not equal null. Now, when I run the program with columns set to 5, we see the message. If I comment out the rows, columns and set up lines and go back to our original constructor with five columns, we still get the incorrect behavior. This is easy enough to fix. I will just use the private method SetColumns within the constructor, taking advantage of the existing validation logic. Changing columns back to an even number, and we get the expected output.
Hallam is a software architect with over 20 years experience across a wide range of industries. He began his software career as a Delphi/Interbase disciple but changed his allegiance to Microsoft with its deep and broad ecosystem. While Hallam has designed and crafted custom software utilizing web, mobile and desktop technologies, good quality reliable data is the key to a successful solution. The challenge of quickly turning data into useful information for digestion by humans and machines has led Hallam to specialize in database design and process automation. Showing customers how leverage new technology to change and improve their business processes is one of the key drivers keeping Hallam coming back to the keyboard.