Classes and Objects
In the Object Orientation chapter the conceptual topics inheritance, polymorphism, and encapsulation were introduced. Every OO language has its own variation of object orientation with some syntactic differences. Not all OO languages share the same features. However, the majority of the concepts and features can be found in the most popular OO languages. In this and subsequent chapters, we will explore how C# implements object orientation. We use the approach of developing a number of classes to demonstrate the OO topics.
To get started, create a new project named ClassesAndObjects. Code Program.cs as shown. Notice the ClassesAndObjects namespace on line 6. The other three classes we add to this project will also use the ClassesAndObjects namespace. Even though we specify the namespace in the other class files it is not required since ClassesAndObjects is the default namespace for the assembly. However, VS automatically includes the namespace directive and we will retain it for clarity. The default namespace of a project can be verified by R-clicking the project name | Properties | Application as shown.
The Rectangle class is specified on lines 8 - 40. Recall that a class is just a blueprint and that no memory is allocated until an object is instantiated from the class. Variables, properties, and methods are all known as members of the class. Lines 10 and 11 contain the instance variables length and width. They are known as instance variables since every object (instance) created from the class gets its own copy of the variable. On the other hand, member variables can also be shared by all objects of the class by using the static keyword which we discuss in the next chapter.
Notice the keyword private on lines 10 and 11. It is known as a "member access specifier". This means that length and width are "private" to the class and can only be accessed by other members of the class. This is an example of information hiding and encapsulation in which access to class members are provided on a "need to know" basis. It is common practice to designate member data (variables) as private and member methods as public. Note that all member methods of the class (e.g. lines 13, 17, 24) are set to public. There is another access specifier (protected) but that is beyond the scope of this introductory course.
In Program.cs, lines 13-16 and 17-23 designate special member methods known as constructors. A constructor method is automatically called when an object of the class is created. Rectangle objects are created on lines 45 and 46 and a constructor is called when each is created. However, since line 45 includes no arguments and line 46 includes arguments, different constructors are called when objects rectangle1 and rectangle2 are created. Class constructor methods must be public and have the same name of the class. So, both constructors are public and have the name Rectangle which is the same name as the class.
The constructor on lines 13-16 is a PDDC (see table). It is programmer-defined since we write it and it is a default constructor since it has no parameters. A default constructor is called when an object is created that does not have arguments in the constructor call. C# includes a hidden default constructor (DC) if no constructors are supplied by the programmer. However, if a non-default constructor is supplied (PDC), then C# requires that a PDDC also be supplied. You are encouraged to test this point by including a PDC without a PDDC.
|DC||Default Constructor||System supplied (hidden)|
|PDDC||Programmer-Defined Default Constructor||Written by programmer|
|PDC||Programmer-Defined Constructor||Written by programmer|
Line 45 is an example of a Rectangle object being created that does not include arguments. That is, the parentheses after Rectangle are empty. Since no arguments are supplied, a default constructor is called. When line 45 executes, the compiler looks for a matching constructor (one that has no arguments) and finds it on lines 13-16. We can see the result of line 45 in the output where the WriteLine statement is written to the command window.
Lines 17-23 specify a PDC (see table) that will be called when two doubles are supplied in the argument list which occurs on line 46. An object of type Rectangle, named rectangle2 is created on line 46. Two arguments ( 10, 20) are supplied in the constructor call. The constructor that matches that signature (a parameter list of two doubles) is located on lines 17-23. Recall from the Object Orientation chapter that this is an example of polymorphism which enables methods of the same name to exist but must contain different parameter lists. The CLR determines which method to run by matching the argument list in the method call (line 46) with the parameter list in the method header (line 17).
The next four methods of the class are known as accessors (getters) and mutators (setters). When we work with properties below, we use the term accessors to refer to both getters and setters. But, when working with separate methods like those in the Rectangle class, we use the more formal terms accessors and mutators to distinguish between getters and setters. Many classes have these two types of methods that are used to work with class data. Getters are used to return object data to the calling location and setters are used to set object data. The getters on lines 24 and 32 return the values of length and width that are stored in the object. The setters on lines 26 and 36 set the values of length and width when those methods are called. The parameter in SetLength() is named paramLength to distinguish it from the member data variable named length. We can also use the keyword this to accomplish the same thing which we do in the Circle class below.
Lines 41-148 comprise the Program class which includes the Main() method of the program and is thus the main class. On lines 45-53 seven objects are created. The type of each object is specified by the class and constructor names used. For instance, on line 49 an object of type Triangle is created with the name triangle1. Notice that on line 49 the default constructor of the Triangle class is called since no arguments are supplied. PDCs are called on lines 46, 48, and 50 since each of those calls contain arguments.
Notice that in addition to Program.cs below, there are three other files for the Circle, Triangle, and Cone classes. Unlike the Rectangle class which was declared in Program.cs on lines 8-40, the Circle, Triangle, and Cone classes are each declared in separate files. To declare a class in a separate file R-click on the project name in Solution Explorer | Add | New Item | Class and name the class file accordingly.
All of the constructors in these classes are similar to those reviewed for the Rectangle class. However, there are a few notable differences. Notice on line 17 of the Circle class that the keyword this is used which is a reference to the current object. The technique used here is to differentiate the parameter radius (which is a variable local to the method) to the instance variable radius which is associated with the object.
The this reference on line 17 refers to the instance variable while radius (on the right side of the assignment operator (=)) is the parameter that was passed when the method was called (line 48 of the Program.cs file). Some developers recommend using this when accessing all instance variables since it enhances code readability. That technique is also used in the PDC in Triangle.cs on lines 18 and 19. The this keyword cannot be used in a static method since those methods reside at the class level and therefore no object is associated with it. More on static methods in the next chapter.
There is another difference in the class constructors. Notice that the Cone class in Cone.cs does not have a constructor listed. When a constructor is not supplied by the programmer, the compiler provides a hidden default constructor (DC). The creation of a Cone object on line 53 of Program.cs uses the hidden DC since no arguments are supplied. Recall that if a PDC is used, a PDDC must also be written since the compiler no longer supplies the DC.
Using Member Methods and Member Data
On lines 55-61 of Program.cs seven double variables are declared and defined. Note that the variables have meaningful names. For instance, triHeight is used to store the height of a triangle. This is considered a "best practice" and should be employed. However, there are times when small identifiers are suitable like as i, j, or k such as iteration control variables or others with limited scope.
For the most part, from this point forward in the course, concepts in the code that have already been covered will not be reviewed. We will focus on the new concepts. On line 70, a mutator (setter) member method SetLength() of the Rectangle class is called (a.k.a. invoked). The variable recLength that was obtained from the user is passed as an argument to the method. When items (variables or literals) are included in a method call like on line 70, the items are known as arguments.
On the other hand, line 28 is the method header and the items in parentheses are known as parameters. Arguments are passed from the method call to the appropriate parameter in the method declaration. The SetLength() method is declared on lines 28-31. The recLength variable is passed to the method and is assigned to the instance variable length on line 30. Analogous steps are performed with the call to SetWidth() on line 74.
On lines 76 and 77 the accessor (getter) methods are called and return the length and width values of the rectangle1 object. The accessor methods of rectangle2 are called on lines 81 and 82. Unlike with rectangle1, the mutator (setter) methods were not called for the rectangle2 object. How then are the length and width variables set for rectangle2? See line 46 for a hint.
The section of code from lines 86-98 works with the circle1 and circle2 objects. Most of the steps are similar to that covered with the rectangle objects. The notable difference is the calls to the CalcArea() member method on lines 93 and 98. The CalcArea() method is declared on lines 29-34 in the Circle.cs file. It is neither an accessor or mutator but rather a member method dedicated to calculating and returning the area of the circle object based on the radius supplied by the user and set by the SetRadius() mutator.
The section of code from lines 102-120 works with the triangle1 and triangle2 objects. The Triangle class uses a new technique known as a property. Properties combine the getter and setter operations in one data type. In Triangle.cs, see the instance member variables tbase and height on lines 10 and 11. The properties associated with the instance variables are on lines 23 and 36. Tbase is the property for tbase and Height is the property for height. It is a naming convention that properties use the upper camel case version of the instance variable name.
Now, back to Program.cs to see the properties in action. Note the use of Tbase and Height on lines 106 and 110. Only the property name is used and the compiler calls the set accessor within the property since the assignment operator is used. Recall with the Rectangle and Circle class the setter methods were called. With properties, the property name is used and the compiler determines whether to call the set or get accessors. Note that the get accessors of the properties are called on lines 112, 113, 118, and 119. Take a look at the properties again in Circle.cs. Notice the get and set accessors.
In the last section of code in Program.cs the Cone class is used. Recall that a constructor is not specified in the Cone class but rather the DC is used. Like the Triangle class, the Cone class also uses properties. On lines 128 and 132 set accessors of properties are called. And, on lines 134 and 135 the get accessors are called. The new technique featured in the Cone class is located on lines 9 and 10 of Cone.cs. ConeRadius and ConeHeight are known as auto-implemented properties (a.k.a auto properties). They have the same capabilities as the properties located in the Triangle and Circle classes but are much more compact. Not only are the get and set accessors provided, it is not necessary to specify instance members (a.k.a. backing fields) associated with the properties. With auto properties, private fields (instance variables) are created automatically and can only be accessed via the auto property get and set accessors.
In the next chapter, we discuss static methods, static fields, and method parameters.