cis 3309 - lecture set e - classes - temple universitycis-iis1.temple.edu/cis3309/lecture set zzz...
TRANSCRIPT
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 1
Under construction
CIS 3309 - Lecture Set E - Classes
(Ver 15: November 13, 2017)
==============================================================
Fundamentals of Inheritance
Go to:
Section E.1 – Introduction Section E.2 – Inheriting from a Class Section E.3 – Base Classes in the .NET Framework Section E.4 – Inheriting Base Class Members – Fields, Methods, Properties Section E.5 – Encapsulation and Designing with Classes and Inheritance Section E.6 – Inheritance and Constructors Section E.7 – Polymorphism and Type Substitution Section E.8 – Replacing Methods in a Derived Class Section E.9 -- Summary
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 2
E. 1 Introduction
{NOTE: I have attempted to convert these notes from VB to C#. If you find
vestiges of VB or C# errors, please let me know. Thanks. FLF} Inheritance is one of the most significant design features of OOP. Inheritance was made popular more than two decades ago by languages such as C++ and Smalltalk. Since then, new languages (e.g., Java, Objective C, C#.NET, VB .NET, Python) have come along and refined the features and syntax for using inheritance. The .NET Framework makes extensive use of inheritance.
The CTS relies heavily on inheritance. When you use the Windows Forms package, your new forms will inherit from an existing form-based class in the FCL.
When you use ASP.NET, your Web pages and Web services will inherit from preexisting classes in the FCL.
As a developer building applications or component libraries based on the .NET Framework, you will find that familiarity with inheritance is an absolute necessity.
Why Bother with inheritance? In fact, why bother with any higher-level language features, such as looping and decision structures, try-catch, methods, classes, etc?
The goal with all of these language features is to provide programmers with tools that are intended to make all aspects of software life-cycle, from design, to testing, coding, debugging, and maintenance, easier and more efficient (and less costly). It is generally agreed that software development using features found in most object-oriented languages should enable us to better model problem domain entities and processes, helping to improve the development process. Do we need these language features to develop our systems? No. Are they believed to be useful over the entire life-cycle of a software product? Many developers think they are.
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 3
E.2 Inheriting from a Class Inheritance allows you to derive new classes from an existing ones, thereby avoiding duplication, and enabling us reuse and enhance existing software components as suggested for entities in a particular problem domain. Definitions (Terminology): Suppose that class C inherits from class B. Then class B is typically called the base class, and class C is called the derived class. (Some refer to B and C as superclass and subclass, or as parent and child classes, respectively. In this material, we will do our best to use the terms "base" and "derived." Contracts – It is useful to think of classes as describing a contract between a client (the class) and objects of the class. A derived class definition automatically inherits the contract of its base class.
Another kind of inheritance, inheritance by Specification is also important, but will not be discussed here.
One of the primary reasons for designing with inheritance is that it provides an effective means for reusing code. For example, you can define a set of fields (attributes), methods, and properties in one class, and then use inheritance to reuse this code across several other classes.
Inheritance is particularly beneficial in scenarios that require multiple classes with much in common but in which the classes are all specialized in slightly different ways. Examples include the following familiar categories: employees (person, administrative, technical, sales), database tables (customers, orders, products), cycles (bicycle, tricycle, unicycle, motorcycle), and graphical shapes (circle, rectangle, triangle).
Example 1: A simple base class
// Base class
public class Person
{
private string hiddenName; // Person's full name
private int hiddenAge; // Person's age
Inheritance by Specialization --
The idea is that the derived class starts with a reusable class definition and modifies it by adding more members or by changing the behavior of existing methods and properties.
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 4
// No-argument constructor
public Person ()
{
hiddenName = ""; hiddenAge = 0;
} // end no-argument Constructor
// Parameterized constructor
public Person (string n, int a)
{
hiddenName = n; hiddenAge = a;
} // end parameterized constructor
. . .
// Private method that calculates the age of a person
private int CalculateAge()
{
int years;
// calculate the difference in years
years = DateTime.Now.Year - hiddenBirthDate.Year;
// subtract another year if we're before the
// birth day in the current year
if (DateTime.Now.Month < hiddenBirthDate.Month ||
(DateTime.Now.Month == hiddenBirthDate.Month &&
DateTime.Now.Day < hiddenBirthDate.Day))
years = years - 1;
return years;
} // end CalculateAge
} // end Person Class
End Class
Basic syntax of inheritance -- Example 1 (above) illustrates the basic syntax a class
named Person that we would like to use as a base class. In C#, when you want to
state explicitly that one class inherits from another, you follow the name of the class with a colon and the name of the base class. Below is an illustration of the basic syntax of
inheritance: a derived class definition for a Student using Person as its base class:
public class Student : Person
{
private string hiddenStudentID;
private string hiddenMajor;
. . .
// No-argument constructor
public Student()
{
hiddenStudentID = "";
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 5
hiddenMajor = "";
} // end no-argument constructor
// Parameterized constructor (with 1 argument)
// Calls the base class first, passing it name
public Student (string name, int age): base (name, age)
{
hiddenStudentID = "";
hiddenMajor = "";
} // end parameterized constructor (with 2 arguments)
. . .
} // end Student Class
In this example,
The Person class serves as a base class for the Student class.
This Student class and any other classes derived from Person will always share
a common set of implementation details, and all will support a unified programming
contract defined by the Person class. (But it is also possible to write
implementations for these derived classes that are quite different from each other as well as to add new members to each class to further increase their specialization, as you will see as you proceed with your own project.)
By default, every class you create in C#.NET can be inherited by other classes. If for some reason you don't want other programmers to inherit from your class, you can create a sealed class. A sealed class is defined in the C# .NET language using the keyword
sealed. The compiler will generate a compile-time error whenever a programmer
attempts to define a class that inherits from such a sealed class:
// Sealed class definition public sealed class Programmer
{ // Sealed class implementation
. . .
} // End Class
public class Programmer : Genius // compile-time error!
Figure E.1 on the next page shows a design view of class definitions known as an inheritance hierarchy.
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 6
Figure E.1 - An inheritance hierarchy
The Person class is located at the top of the hierarchy.
The Client and Employer classes inherit from the Person class and are
located directly below it in the inheritance hierarchy.
Notice the direction of the arrows when denoting inheritance.
Manager and Worker have been defined with Employee as their base class.
They inherit indirectly from the Person class. Thus a class in a multilevel inher-
itance hierarchy inherits from every class reachable by following the inheritance arrows upward.
For example, you can correctly say that a programmer "is a" person. You can also correctly say that a senior programmer "is a" programmer. The purpose of the “is-a” test is to ensure that a derived class is designed to model a more specialized version of whatever entity the base class is modeling. MODEL is the key term here!! All classes model something. And, virtually
everything we do in writing programs involves constructing models of processes and entities.
Person
Client Employee
Manager Worker
Design Rule –
Two classes that will be related through inheritance should be able to pass the is-a test.
-- If it makes sense to say "C is a B," then it makes sense for class C to inherit from class B.
-- If such a sentence doesn't make sense, then you should reconsider the use of inheritance in this situation.
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 7
You should try never to establish an inheritance relationship between two classes that cannot pass the “is-a” test. Example 2: Suppose you have a cycle class hierarchy with a cycle at the top and a unicycle, bicycle and tricycle (remember those?) all inheriting from the cycle. It might be tempting now to put such items as wheels, tire, handlebars, etc., (also modeled using classes) somewhere in the hierarchy. You should resist this temptation! Wheels, tires, handlebars, etc., cannot pass the "is-a" test for any of the classes just discussed. The three items we just listed should be treated as additional attributes of the cycle entities. [You can correctly say that a bicycle "has a" wheel (or two), but that relationship calls for a different design technique that doesn't involve inheritance. When you determine that two entities exhibit the "has a" relationship, the situation most likely calls for a design using containment, in which the Wheel class (for example) is used to define fields (attributes) within the Bicycle class (for example). In other words, the Bicycle class would likely contain one or more wheel objects (instances of the Wheel class.]
E. 3 Base Classes in the .NET Framework Now that you've seen some basic principles and the syntax for using inheritance, it's time to introduce a few important rules that imposed by the .NET Common Type System (CTS).
Example 3: When you define a class without explicitly specifying a base class, the compiler automatically modifies the class definition to inherit from the Object class in the FCL. Thus, the following two class definitions have the same base class:
// Implicitly inherit from Object
public class Cat {}
// Explicitly inherit from Object
public class Cat : System.Object { }
Note that every structure and every enumeration also has a base type. Recall from our earlier study of data types that the inheritance hierarchy of the CTS is singly rooted because the system-defined Object class serves as the ultimate base type.
.NET Rules Governing the use of Inheritance -- 1. You cannot define a class that inherits directly from more
than one base class. (As is the case with Java and VB, CTS does not support multiple inheritance.)
2. You cannot define a class that doesn't have a base class.
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 8
Every other class either inherits directly from the Object class or inherits from another class that inherits (either directly or indirectly) from the Object class.
E.4 Inheriting Base Class Members – Fields, Methods, Properties We begin by recalling the levels of class member accessibility discussed in a previous lecture set. These are summarized in the next box. Keeping these definitions in mind we can briefly summarize how inheritance works.
Every public or protected member (field, method, and property) that is part of a base class definition is inherited by the derived class definition.
Each object created from a derived type carries with it all the states and behaviors that are defined by its base class.
Code in a derived class has direct access to the public and protected members inherited from its base class. However, access to the private members of the base class must be programmed using the public methods in the base class.
Constructors are not inherited. We will have more to say about constructors in Section E.6. For now, we suggest that you always write at least a base constructor for all of your classes.
Five Levels of Accessibility -- Private. A base class member defined with the Private access modifier is not acces-sible to code outside this class. This no other class, derived or otherwise, can directly access this member. Protected. A base class member defined with the Protected access modifier is directly accessible to code inside the defining class and any other class derived from this class. But it is not accessible to any other class. Public. A base class member defined with the Public access modifier is directly accessible to code inside any class. Internal. A member that is defined with the Internal access modifier is accessible to all code inside the containing assembly but inaccessible to code in other assemblies. The internal level of accessibility is not affected by whether the accessing code is part of a derived class. Protected Internal. The protected internal level of accessibility is achieved by combining the Protected access modifier with the Internal access modifier. A protected internal is accessible to all code inside the containing assembly and to code within derived classes (whether the derived class is part of the same assembly or not). This is illustrated further in the case of Class F in Figure E.3 shown later.
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 9
What Goes On Behind the Scenes? Constructors aside for now, one view of how inheritance works is depicted in Figure E.2. Figure E.2 – Visualizing a Base Class as Seen “Through” a Derived Class
As this figure shows, all members (methods and data stores) of the base class are inherited by the derived class (all depicted in the white squares). All rules of accessibility to these inherited base class members are the same as for the base class. Thus, the public and protected members of the base class are directly accessible to the derived class. The private members of the base class are still encapsulated and accessible ONLY through public methods of the base class. One way to view this is to consider the base class as “becoming part of” the derived class. Remember too, that the derived class may contain its own private, protected, public, etc. members (shown in yellow). In Figure E.2, we indicate this accessibility for
Derived Class
Public and Protected Additional Data Stores (if any)
Additional Methods
Private Hidden Additional Data Stores
Hidden Additional Methods (if any)
(Accessible ONLY to this derived
class)
Base Class
Private Hidden Base Data Stores
Hidden Base Methods
(if any) Not directly
accessible to derived class
Public and
Protected Base Class Data Stores (if
any)
Base Class Methods
(directly accessible to
derived class)
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 10
both the derived and base classes using a dotted box to enclose public and protected class members. The private members of the base class, and any additional private members of the derived class, are accessible only through the use of inherited public methods of the base class. They are enclosed in solid line boxes. In the EmployeeManagerClient example illustrated in Figure E.1 this “becoming part of“ concept implies, for example, that the three attributes of a Person become part of a Manager object. Person may be an abstract class (which cannot be instantiated), but it does not have to be instantiated for its attributes to become part of a Manager object. However, these three attributes, can only be accessed according to the access rules shown in the Five Levels of Accessibility box shown previously. Thus, if the attributes of the Person class are private, which is what I generally prefer, they can only be accessed through the methods in the Person class. The following example and accompanying diagrams (see Figure E.2A – E.2C) illustrate
these points for a small code sample. Note that name and age are assumed to be
properties defined in the Person class even though they are not shown (You could
easily write them). Example 4: Inheritance and Access Modifiers – How it all works
public class Person
{ . . .
private string hiddenName;
private int hiddenAge;
// Parameterless constructor
public Person() {
hiddenName = "";
hiddenAge = 0;
} // end parameterless argument constructor
// Parameterized (Overloaded) constructor
public Person (string n, int a) {
hiddenName = n;
hiddenAge = a;
} // end parameterized constructor
public bool commitToDB() { . . .} // not a virtual method
} // end Class
(continued on next page )
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 11
// Subclassing for Specialization
public class Borrower : Person
{
// Specialization - adding a new data store
// (not part of Person class)
private double hiddenCreditLimit;
public Borrower() {
// Call parameterless Person constructor here
hiddenCreditLimit = 0.0;
} // end parameterless constructor
public Borrower (string n, int a, double s) {
// Call the parameterized constructor here
// Pass it n and a.
hiddenCreditLimit = s;
} // end overloaded constructor
public bool new commitToDB() { // hides the parent version
. . .
} // end commitToDB
. . .
} // end Class
Figure E.2B provides another view of how all of this works, a view that is specific to the current example. It shows that all public and protected members of the base class (in our case, the class Person) are inherited in the derived class (in our case, the Borrower class). These derived class members are therefore visible to any user code in the same way as are the members of the base class. In addition, the derived class may contain its own private, protected, public, etc. members. Should there be any naming conflicts between a base class and a derived class, usually
caused by hidden or overridable (replaceable) functions such as commitToDB, we can
distinguish one member from another using the prefixes base and this. For example,
base.commitToDB()
used in the derived class Borrower represents a call the version of commitToDB
found in the base class Person. On the other hand
this.commitTODB() or simply commitToDb()
used in the derived class Borrower references the "current instance" of commitToDB
(found in the Borrower class) .
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 12
Figures E.2 and E2.A illustrate the most important point about inheritance. To reiterate:
Figure E.2A – A Derived Class and Its Related Base Class
Figure E2.A Another Illustration of Inheritance
Person Object
Borrower
Object
Object
Hidden
Name
Hidden
Age
name
age
Hidden
Credit
Limit
creditLimit
toString
commitToDB
A public derived class inherits the data stores of the base class. It also inherits and possibly redefines the methods of the base class. Each object created from a derived type carries with it all the states and behaviors that are defined by its base class. New fields (attributes), properties, and methods may also be added in the derived class. The behaviors of the fields in the base class are not changed by the inheritance. So it is as though an object of the base class becomes a part of the derived class whenever an object of the derived class is instantiated.
commitToDB
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 13
***** Skip this – go to End Skip 1 ***** Another way to illustrate this point is shown in Figure E.2B. As shown in this Figure, the
public properties age and name and the public method commitToDB are all inherited by
the Borrower derived class from the Person base class. Had there been any protected
members of the Person class, they too would have been similarly inherited by the
Borrower class. [Note that we obtained the list of accessible members in the Borrower
class simply by typing “borrower1.” following the declaration of a type borrower1
object in the code shown.] Remember that there is a replacement version of
commitToDB in the Borrower class and that all calls in this class to this method, if not
prefixed with base will refer automatically to the replacement function.
Figure E.2B – Inherited Public Members of a Derived Class (uses VB code)
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 14
An examination of the class hierarchy constructed for the code shown for Example 4 also serves to illustrate this point (see Figure E.2C). These class hierarchies can be viewed by right clicking on the top (Project Line) in the Solution Explorer window and then selecting View Class Diagram from the window just opened. The top part of Figure E.2C
shows that class Borrower inherits from class Person. It also shows all the details of
the members of the class Person that are inherited as well as the details of the additional
members of the Borrower class defined in that class.
Figure E.2C – Class Hierarchy for Example E.4 Showing Members of Person Class
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 15
Example 5: The meaning of access modifiers – A Graphic View
Figure E.3 illustrates the implications of using the different access modifiers in code involving numerous classes in two different assemblies.
Figure E.3 Five Levels of Accessibility – An Illustration
The following statements are all related to the Assemblies I and II and the class relationships illustrated in Figure E.3 below. These are a repeat of the Five Levels of Accessibility box shown earlier in this section, but they relate specifically to Figure E.3.
1. All private members of class A are accessible to none of the other classes in the figure (including even those that are derived from class A..
2. All protected members of class A are accessible to the classes derived from A, namely, classes B, C, and F (even though F is in a different Assembly).
3. All protected internal members of class A are accessible to derived classes inside Assembly I (classes B and C) but not to derived classes outside Assembly I (class F).
4. All public members of Class A are accessible to all other classes in the figure.
A
C B
D E
Classes used …
Assemblies used …
Assembly I
Assembly II
Classes used …
A, B, C, and F are part of one class hierarchy with base class A
G
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 16
5. All public internal members of Class A are accessible everywhere (to all classes) in class A's Assembly but not outside this Assembly. Thus, such members are accessible to classes B, C, D, and E, but not to class G. Note the exception here: public internal members in classes A and C may be accessed in Class F even though F is in another assembly.
***** End Skip 1 Begin reading here again *****
E. 5 Encapsulation and Designing with Classes and Inheritance Encapsulation is the practice of hiding the implementation details of classes and assemblies from other code. Terms such as information hiding, and limiting the need to know are also used when referring to the concept of encapsulation and object-oriented design and programming. Low coupling and high cohesion are also terms used. For example,
A private member of a class, A, is hidden from ALL other classes in an application
A protected member of a class A is hidden from all other classes in an application other than those derived from A or its descendants
An internal member of a class A is hidden from code in other assemblies.
E. 6 Inheritance and Constructors (a bit complicated) The way in which constructors are handled isn't as obvious. Unless you want to delve into more of the constructor handling complexities, it might be easier to follow a few basic suggestions indicated in this section.
Bottom Line – Be as restrictive, keeping access to class members carefully
guarded. Be sure you have good reasons for loosening permission access.
Constructive Constructor Suggestions 1. Always write a no-argument (parameterless) constructor for every class you design. Do this even if you also write some parameterized constructors in the same class. 2. Your parameterless constructor can have no code body, but you might wish to use it to ensure that initializations of class variables (even just to blank or null strings, or 0 numeric values, etc.) are handled, even if no other initializations seem to be needed. 3. Make sure each constructor in a derived class calls its corresponding base class constructor before it sets about to do its own work. 4. It is true that the compiler will take care of some of these items for you, but it is best not to leave anything to chance. 5. It is also a good idea to ensure that all constructors are visible in each class, as opposed to simply generated by the compiler. 6. Parameterized constructors in a derived class do not need the same interface as in the base class, but if you are going to pass arguments through to the base class, these arguments must appear in the derived class as well. (See examples in Project IV code.)
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 17
Remember that constructors in an inheritance hierarchy are not inherited. Furthermore, they must be executed in the reverse order. Thus in Figure E.3, any constructor in Class F must execute a constructor in Class C before carrying out its own work. In turn, the constructor in Class C must call a constructor in class A before carrying out its tasks. If you do not explicitly call these constructors in the order just described, the compiler will generate these calls for you. Remember:
1. If the derived class a parameterless constructor, the base class must also have a parameterless constructor.
2. Each constructor defined in a derived class must explicitly call one of the accessible constructors in its base class before performing any of its own initialization work.
Example 6: Consider the following class definition:
// A class you write
public class Dog {
. . .
} // end class Dog
Once compiled, the definition of this class might look something like this:
// Code generated by compiler
public class Dog : System.Object {
// Default constructor generated by compiler
public Dog() {
Base.Dog(); // Call to default constructor in System.Object
// All other code to be executed by Dog objects
} // end default constructor
} // end class Dog
As this example reveals, the compiler will generate the required constructor automatically along with a call to the base class's default constructor. But what about the situation in which the base class does not have an accessible default constructor? This can easily happen in any class in which the author writes one or more non-default constructors of his (or her) own. In such a case, the derived class definition will not compile if it contains a constructor that does not have an explicit call to a base class constructor at the beginning of its code. Note that the Object class contains a public default constructor. ***** Skip to End Skip 1 *****
The best practice you can adopt when writing constructors for derived classes is to ALWAYS write the call to a base class constructor for any constructor you write for a derived class.
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 18
Sequence of Execution of Constructors Here we consider the sequence in which derived class constructors are executed during object instantiation. To understand how this works, we examine the scenario where you instantiate an object of the type Worker.
When you instantiate a Worker object, a constructor in the Worker class begins to execute (which constructor is selected depends upon the arguments you use when you instantiate the object).
This constructor must call a constructor in its base class (Employee, in this case). (This will happen if you write such an explicit call or if the compiler generates a call to the base class default constructor).
The constructor in the base class faces the same constraints. It must call the default constructor of its parent class (Person in our case).
The important observation is that constructors must execute in a chain starting with the least-derived (top-level) class (i.e., Object) and ending with the most-derived (bottom level) class. The implementation for the constructor of the Object class always runs to completion first. In the case of creating a new Worker object, when the constructor for the Object class completes, it returns and the constructor for the Person class and then to the Employee class, all of which must run to completion before the Worker constructor can run.
E.7 Polymorphism and Type Substitution (Replacement) Inheritance was presented as an effective way to reuse the implementation details of a base class (via construction of derived classes). Another aspect of inheritance lies in its support for polymorphic programming. Polymorphism Every derived class inherits the programming contract that is defined by the accessible members of its base class. As a result, you can program against any object created from a derived class by using the same programming contract (that is, the same method with the same argument list and return value) defined by the base class. In other words …
Inheritance provides the ability to program against different types of objects using a single programming contract. This is known as polymorphism (from the Greek meaning "having multiple forms").
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 19
This polymorphic programming style is a powerful programming technique because it allows you to write generic code. To put it another way, the code, which is written against the base class, is also compatible with any of the derived classes, because they all share a common, inherited design. The derived classes are thus plug-compatible from the perspective of your code, making your code more applicable to a wider range of situations. (Look at some Project IV examples – save and display for example). Polymorphism can be envisioned as the notion that an operation (method) supports (may be used on) operands of one or more types (defined using classes). For example, you can write "X + Y", and the + operation will work in .NET whether X and Y are both integers, floats, decimals (etc.) or strings. Hence the operator "+" is polymorphic, and the code "X + Y" is an example of polymorphic programming. Example 7 (simple operator overloading):
a.) We could define a fraction class (using integer numerators and denominators) that
overloaded the operations +, -, *, /, as well as some basic I/O functions. Then if f1
and f2 were two instantiated objects of that class, the operation
f1 + f2
would make sense. (How would you write the code that carries out this operation using integer operators only?)
b.) The same argument could be made for a set class (How would you define the +
operation on sets?) c.) On the other hand, the + operator defined on a student class might make much
less sense and probably would not be defined for such a class. It also makes little sense when defined on the character data type. Try writing the code
a = ‘b’ + ‘c’;
in C# (with a of the character data type) and see what you get. Try it in C. It
actually works. Do you know why? You are already familiar with the idea of overloading as it has been used in many lan-guages. In fact, overloading has been used in higher level programming languages since the mid 1950s, long before the class concept was introduced. The use of overloaded operators in the case of the fraction class illustrates how overloaded operators can be extended using classes to define additional “versions” of the same operator.
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 20
Static or Compile-Time Binding It is important to emphasize the role of the compiler in determining the type of operation (integer, float, fraction, etc) to be performed. In the next example, we examine another situation in which the compiler acts to decide which version of a method to select. These two examples (Examples 8 and 9) involve the use of static (compile-time) binding. But lurking in the wings is an even more interesting situation, dynamic, execution-time binding, where the determination of the method to be used is done during execution, based on the type of the object preceding the “.” in the call of the method. You have actually seen and probably implemented static method binding when writing multiple constructors for a class. [How did these constructors (all having the same name) differ from one another? And, how did the compiler determine which constructor to call in each case?] In Example 8, we examine a slightly different version of static method binding.
Example 8: Method overloading or static replacement adds yet another dimension to polymorphic programming. This is a bit more complicated, because the compiler now must examine the argument list used in the call in order to determine which version of the
method gets called. For example, in the following, the WriteLine method is overloaded: public static class Console {
public void WriteLine();
public void WriteLine(string value);
public void WriteLine(bool value);
...
}
Note carefully that all three of these methods are defined in the same class, a requirement when overloading is used. In this example, the C#.NET compiler will generate the call to the implementation of
WriteLine(string value)when it determines that the caller is passing a string
argument (such as "frank" or name). It will call the version of WriteLine (bool
value) when it sees a call such as WriteLine(flag); assuming the flag is a type
bool variable.
Note carefully that in this discussion of simple operator overloading (using the + operator in this case), we assume the existence of many different versions of the same operator, all having the same name (plus) and the same list of arguments. The compiler determines which operator to apply simply by examining the type of the operands involved in an operation.
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 21
Polymorphism is a very powerful feature of object-oriented programming. It allows you to program in more generic terms and to substitute (replace) different compatible implementations for one another. To design, write, and use class definitions in C# .NET that benefit from polymorphism, however, you must understand a number of additional concepts. The first is the difference between an object type and a reference variable type. Example 9: (From the Employee-Manager Project) -- on overloadable methods in which
The argument list is used to determine which method is dispatched by a call
The argument list involves the use of references to objects that are instances of classes in a class hierarchy
This example is based on the class hierarchy shown in Figure E.4 (taken from your Final Project Employee-Manager example). Figure E.4 Product Hierarchy in the Employee-Manager Example.
As shown in Figure E.4 for the EmpMan Project, we have 5 classes. For the leaf node classes (Client, Manager, and Worker) we can create objects and reference those objects in the project. For the Person and Employee classes, we are not allowed to create
In all polymorphic cases (thus far), the COMPILER determines the version of the method that that is used by examining the operands or arguments used in the call and choosing the version of the method with matching operands. If there is no such version, the compiler will generate an error.
Employee-Manager Type Hierarchy (abbreviated version)
Person
Client Employee
Worker Manager
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 22
objects, as they are declared to be abstract. This enables us to model all the entities in
our system without necessarily creating objects for each.
As the user of the EmpMan system creates objects of type Client, Manager, or Worker, these objects are added to a list of Persons. This addition of an object to the list of persons might be done as shown next. The list itself is created and hidden in
PersonList class using the declaration
private List <Person> HiddenPersonList = new List<Person> ();
The code to add an item (Client, Worker, or Manager object) to the list is also in the
PersonList class:
// Add a Person to the List public void Add(Person e) { HiddenPersonList.Add(e); } // end Add Worker, Client, or Manager Object to the PersonList
This add method can be called by any of the following three statements.
thisPersonList.Add(thisClient); thisPersonList.Add(thisManager); thisPersonList.Add(thisWorker);
Here we assume there is a declaration PersonList thisPersonList = new PersonList();
in the same class as the calls to Add.
The 3 types of objects (Client, Manager, and Worker. are
subtypes of the Person type, yet all of them may be added to the
List (of Person) declared above. Thus a list may contain objects of
different types as long as the types of these objects are subtypes of
the base type (Person in this case) of the list elements. This seems
simple enough, but as we shall see in Section E.8, it creates some
interesting problems if we want to process (for example, display)
elements of this list. More on this to come.
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 23
E.8 Replacing Methods in a Derived Class
Dynamic Binding -- How members of a class are accessed at runtime Static and dynamic binding represent two different ways in which a client can invoke a method or property of an object. Static binding is usually more straightforward and results in better performance. With static binding, determinations of which version of a class member (such as a property or method) is called is done at compile time. We have already examined a number of examples of static binding, and we will look at one more here. For example, if I wish to save information about a Client, Manager, or Worker object, I can use calls to the corresponding version of the Save method contained in these classes: Client thisClient = new Client();
thisClient.Save(this);
or
Manager thisManager = new Manager();
thisManager.Save(this);
or
Worker thisWorker = new Worker();
thisWorker.Save(this);
Even though the argument lists in these calls are the same, there is no confusion for the compiler as to which version of the method Save is being called. This is completely determinable at compile time by the type of the object specified using the dot notation.
Now, however, suppose we to display the contents of an item in the Person list. private List <Person> HiddenPersonList = new List<Person> ();
We could write a method call such as HiddenPersonList[i].display();
However, recall that the items in the list can be of three different types, Client, Manager, or Worker. Since at least some of the attributes of these three data types are different,
each has its own display method. But, looking strictly at the syntax of the above call,
we have no way of knowing at compile time the type of the object to be displayed and
therefore, no way of determining which display method to call. This is where dynamic
binding comes into play.
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 24
Dynamic binding yields greater flexibility and is the underlying mechanism that allows a derived class to replace behavior inherited from a base class at runtime. It is one of the crucial ingredients of polymorphic programming. Recall that with static binding the type of the reference variable (referencing an object) object that precedes a class member reference is used to decide at compile time which class member (method or property) is being accessed. For example: solutionObject.compare(guessAreaEntry[i]);
Here the reference variable is solutionObject and its type is known at compile time
(type SolutionClass, perhaps). But in the example just shown,
HiddenPersonList[i].display();
the type of the object being referenced by the reference variable is not known to the compiler, but can only be known at execution time when an object is actually stored in
HiddenPersonList[i]. Only then can the type of the referenced object be
determined.
We now look at another example involving the use of C# lists simply to reinforce what we have said to this point. In this example, as in the previous one, there is no way of determining at compile time the type of object being referenced by any entry of the list. All
list entries are defined as references to objects type of type Person. As such, however,
they can contain objects of type Person or any of the types derived from Person.
Example 10: The Person Class in the Empman Project contains its own version of the
tostring method. The Person version is illustrated below.
public override string ToString()
{
string s = "ObjectType : " + base.ToString() + "\n";
s += "PersonName : " + HiddenPersonName + "\n";
Static binding is the default access (invocation) mechanism used by C# .NET and by the CLR. It is based on the type of the reference variable, not the type of object being referenced used in the reference to a method. When dynamic binding is used (involving runtime replacements of class members and
the keywords override), the determination as to which method is called is made during
execution based up the type of the object involved in the access expression. With static binding, the compiler can perform more of the work. For this reason, static binding can provide a measurable performance advantage over dynamic binding.
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 25
s += "PersonBirthDate: " +
HiddenPersonBirthDate.ToShortDateString() + "\n";
s += "PersonID :" + HiddenPersonID;
return s;
} // end ToString
This method builds a string by concatenating the name, birthdate, and ID of a Person.
This version overrides the base class (Object Class, in this case) version of ToString.
In addition, the subclasses of the Person class have their own versions of ToString
each of which are referenced as follows with a clause of the form x.ToString(). For
example, the version of ToString in the Worker class looks like this:
public override string ToString()
{
string s = base.ToString() + "\n";
s += "WorkerHourlyPay: " + HiddenWorkerHourlyPay.ToString();
return s;
} // end ToString
The existence of these ToString methods raises a number of issues. But the simple
fact that we want to illustrate here has to do with the determination of the version of
ToString that is called.
Question 1: Which version of ToString is called in the line
HiddenWorkerHourlyPay.ToString("C") ANSWER: The version that is called is determined wholly by the type of the object that
precedes the reference (HiddenWorkerHourlyPay in this case). This type is
determined by the compiler at compile time. We say that any such determination done at compile time is considered to be a static determination. To state this in another way: we
say that the version of ToString that is to be called is bound (determined) at compile
time. Question 2: Consider the following declaration: private List <Person> HiddenPersonList = new List<Person> (); In the reference HiddenPersonList[i].toString()
how does the compiler know which version to call?
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 26
ANSWER: The compiler does NOT know which version of toString to call (access)
because the Person version of toString is defined so as to be overridden by a specific
product version (such as Manager or Employer) at runtime. In this case, the
determination as to which version of toString is to be accessed has to be made at
runtime, after the type of the object referenced by HiddenPersonList[i] has been
determined. Thus, if we assigned a Worker object to HiddenPersonList[i] then the
Worker class version of ToString would be called. But the assignment of objects to the
list is done at run time, so only at run time can we determine the type of the object store
in HiddenPersonList[i] . (If no object had yet been assigned at the time of this
reference, an exception would be raised.) Note that this approach to resolving references carries with it considerable "effort" for both the compiler and the runtime system (the CLR). All the type information involved has to be carried along at run time with the data stored in the list. The compiler has to provide all this information, and the CLR has to use it during execution in order to
determine the correct version of ToString to use in the above reference.
Question 3: Is the version of ToString in the Person Class ever used? Why or
why not? If not, why bother having it?
ANSWER: The Person version of ToString is used even though there are no
Persons per se but only products derived from the Person class.
However, there are numerous examples in which specific members of a base class have no work to do because all work is done by the overriding members of the derived class. In
such cases, the base class member is still required as an abstract member, but it has
no implementation: public abstract string findStudent ();
Class members marked as abstract include no implementation code of their own (and
they have no body either). It is also not possible to instantiate objects of an abstract
type. Any class that contains one or more abstract members should be marked with a
virtual keyword.
NOTE: If you want to go to extremes and write an abstract class with only definitions and no implementations, you can do this. Such a class is called an Interface in C# .NET)
The Person class might be suited to this idea in the EmpMan Project, because we do not
intend to ever instantiate any objects of type. Even so, there are some members of the Person class that have useful implementations to be inherited by derived classes, so we chose not to make the Person class an interface.
Note that all Interface members are automatically public so access modifiers are not needed. Interfaces can include functions, events, properties, other interfaces, classes,
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 27
and structures. Interfaces can also inherit from other interfaces. Classes derived from an interface can pick and choose the members they implement – no one derived class has to implement all members of an Interface. There is lots more to be learned about interfaces, but we will not delve into this topic any further in this document. Dynamic Binding and Overridable Methods (review)
Recall that we claimed earlier in this chapter that one of the most valuable features of inheritance is the fact that the designer of a base class can provide default behavior that can optionally be replaced by a derived class author. Often, a base class author can provide a default implementation for a method. A derived class author can then choose whether to use the base class implementation or to write a more specialized implementation. In some cases, a derived class author even has the option of extending the base class implementation by adding extra code to the derived class. Using a derived class to replace the implementation of a method or a property defined in a base class is known as overriding. Method overriding relies on dynamic binding, not static binding. Overriding a method in a parent class requires that the parent method be
written with the modifier virtual(in C# (overridable in VB). You will note that both
the Save and Display methods in the Person class were written using this modifier so
that they could be overridden by the Save and Display methods in the subclasses of
the Person class.
Dynamic binding takes into account the type of an object at runtime, which gives the CLR a chance to locate and call the overriding method. In this way dynamic binding supports more flexible code, albeit with a runtime cost. Dynamic binding is the default in the Java programming language and, in fact, the only option in Java in most cases. It is sometimes useful to use a picture to help remember what a list such as
HiddenPersonList[i] really looks like. Figure E.5 on the next page illustrates what
this list might look like, although it is not an expertly drawn picture. Just for fun, we added two other types, a Student type (a derived type fro Person) and a Faculty type (a derived type from Employee). Try redrawing the picture in Figure E.1 with these two new types added.
It is important to note that this type of dynamically (runtime) determined dispatch requires the use of overridable methods with the same name and the same argument list.
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 28
Figure E.6 HiddenPersonList – with 3 items of different types
Chaining Calls from a Derived Class to a Base Class (can be skipped)
When you override a method, it's fairly common practice to chain a call from your overriding implementation in the derived class to the overridden implementation in the base class. This technique allows you to leverage the implementation provided by the base class and extend it with extra code written in the derived class. Consider the following reimplementation of the Programmer class: public class Worker : Employee
{
. . .
public override void Save(frmEmpMan f)
{
base.Save(f);
HiddenWorkerHourlyPay =
Convert.ToDecimal(f.txtWorkerHourlyPay.Text);
} // end Save
. . .
[0]
[1]
[2]
[3]
[4]
[5]
Objet of
type
Worker
Object of
type
Manager
Object of
type
Faculty
Object of
type
Manager
Object
of type
Worker
Object of
type
Student
Object
of type Client
[6]
HiddenPersonList
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 29
The C# .NET keyword base is used in a derived class to explicitly access public or
protected members in its base class. In this example, the Worker definition of Save
makes an explicit call to the Employee implementation of Save. This approach allows the
derived class author to reuse and extend the method implementation provided by the base class author. (Although not shown, note too that the Employee class version of
Save in turn references the Person version, extending our chain in this example to three
levels.) Note that chained calls do not have to be made at the beginning of the derived class implementation. They can be made at any point in the overriding implementation. Design Issues with Overridable Methods (can be skipped) Anyone who has managed a large software project using inheritance and method overriding can tell you that managing the semantics of overridable methods and properties requires a high level of expertise and attention to detail. An overridable method complicates the programming contract of a base class because a derived class author can use any of three possible approaches:
inherit a base class implementation and reuse it without modification.
provide an overriding implementation that chains a call back to the base class implementation (extends the original method).
provide an overriding implementation that does not chain a call back to the base class implementation (replacing the original method).
While the CLR's support for method overriding allows for reusing, extending, and replacing, many overridable methods have semantics that do not support all three approaches. We will not pursue these issues in this Lecture Set as it is a topic for a more advanced discussion on the method overriding in VB. Example 11: The game of Monopoly. Please click on the word Monopoly to see a full explanation of another example of the benefits of using inheritance with function overrides, chaining, etc.
E.9 Summary
As you read through this material, one theme probably became clear: The use of inheritance increases your responsibilities both during the design phase and during the coding phase. If you decide to create a class from which other classes will inherit, you must make several extra design decisions.
Appendix E – Inheritance 11/14/2017 4:07:55 PM Page 30
If you make these decisions incorrectly, you can get yourself into hot water pretty quickly. This is especially true when you need to revise a base class after other programmers have already begun to use it. The first question you should address is whether it makes sense to use inheritance in your designs. In other words, should you be designing and writing your own base classes to be inherited by other classes? Once you've made a decision to use inheritance in this manner, you take on all the responsibilities of a base class author. It's your job to make sure that every derived class author understands the programming contract your base class has defined for its derived classes. While you may never create your own base class, you will more than likely create custom classes that inherit from someone else's base class. It's difficult to imagine that you could program against the classes of the FCL without being required to inherit from a system-provided base class such as the Form class, the Page class, or the WebService class. Because so many of the class libraries in the .NET Framework have designs that involve base classes, every .NET developer needs to understand the responsibilities of a derived class author. If you don't have a solid understanding of the issues at hand, you will find it difficult to implement a derived class correctly.