- Explain type compatibility under inheritance.
- Describe static and dynamic method binding.
- Discuss the details of dynamic binding:
- Show how to use dynamic binding and polymorphism to write generic code.
- Cover downcasting and type testing.
The most obvious advantages of inheritance are that it helps eliminate repeated code and
allows program structure to mirror the organization of the real world. When inheritance
is combined with reference compatibility and dynamic method binding things get even more
exciting. Clients can then write extremely generic client code that works correctly for
any type in the hierarchy. Dynamic binding ensures the correct code is executed for the
type of object being processed.
C# inheritance models the "is-a" relationship so a derived class inherits all the
behavior of its base class. To explore the implications of this idea, we will return to
the simple person/student/employee inheritance hierarchy which models people at a
Suppose the base class person contains a public method named
and some supporting fields.
Now we derive a student class from the person class. The student class inherits the
Birthday operation and adds a public
SetId method and a
Typical client code using the student class might look like that shown below.
Student object is created and a
is used to refer to the object. Using the
Student reference, the
client code can invoke the inherited
Birthday method and the
Things get more interesting when we consider the concept of type compatibility.
Type compatibility is a natural consequence of the fact that inheritance models
the is-a relationship. The formal way to express this idea is to say something
like "a derived class object has all the behavior of its base class and so can
be used in any situation where a base class object is expected." In practice this
means that we are able to use a base class reference to refer to any derived
class object. In our person hierarchy, we could use a
to refer to a
Student object. The assignment is legal because
In fact, a
Person reference can refer to any object in the hierarchy.
Let's put aside for the moment the major applications of this technique and
simply examine the power and limitations of the code. The major advantage of
using a base reference is that our code becomes a little bit more generic
since only the type of the object is hardcoded. If we later decided to use a
different type of object, say an
Undergraduate, we would only
need to modify the
new statement where the object is created
while the type of the reference used to manipulate the object would not have
to change. The key limitation is that we are only allowed to access the
Person part of any object when using a
This is a key concept that shows up in many different situations so it's probably
worth repeating. The type of the reference determines what access is allowed to
the object. If the reference is of type
Person, then only
members can be accessed regardless of the actual type of the object. The object might
Undergraduate that supports many other useful operations; however,
they will not be available through a
Despite this limitation, the use of a base class reference is an important
and powerful technique. To begin to see the benefits, consider the following
CheckAge method which has a parameter of type
Because the parameter is a base class reference, any derived class object can be
passed as the argument. The
CheckAge method is said to be generic
because it works for all people.
Without the type compatibility provided through inheritance it would be painful
to support the
CheckAge operation for each type in the hierarchy.
Individual methods would need to be written for each type. The ability to write
a single method that works for all types in the hierarchy provides a nice savings
in code, coding time, and maintenance effort.
Classes from an inheritance hierarchy often contain methods which logically perform
the same operation. For example, every class in a hierarchy of different types of
employees would naturally offer a
Each class would implement their
Promote method as appropriate to their type.
Employee version would likely be the trickiest to code since
it is difficult to know how to promote a generic employee. We'll solve this problem by
simply putting an empty body for the implementation.
Faculty promote method would of course involve a salary increase.
In addition, faculty members might have their level increased each time they are
promoted. When they reach a certain level they are rewarded with tenure so they
can pursue long term research without the need to continuously publish.
Staff version of promote would probably be quite simple with only a
small increase in salary.
Now things get quite interesting since
objects have two
Promote methods, the one they inherit from
Employee and their own version. To see the issue, consider the following
client code. The
Evaluate method has a parameter of type
Employee. Type compatibility means we can pass an
Faculty, or a
Staff member. Inside the method, the
Promote method is called. Which version should be invoked?
The process of deciding which method to invoke is called binding. There are two types
of binding which are called different things in different programming languages.
The first type is called static binding in the .NET world. Other names for this
type of binding are early binding and compile time binding. Static binding decides
which method to call based only on the type of the reference. In the
Evaluate example static binding would always choose the
Employee version of
The other type of binding is called dynamic binding in .NET. Other names for this
concept include runtime binding or late binding. Dynamic binding decides which
method to call based on the actual type of the object involved. In essence, dynamic
binding puts off the binding decision until runtime when the actual type of the
object is known. At runtime, the dynamic binding mechanism determines the type
of the object and invokes the appropriate version. In the
example, if the parameter happens to be a
Faculty member then the
Faculty version gets called, if it is a
Staff version would be invoked.
The employee example has been carefully constructed so that dynamic binding is
appropriate for the
Promote operation. The key observation is that
Promote methods that appear throughout the hierarchy are all
version of the same method. That is, the details of their implementations are
of course different but conceptually they all perform the same operation.
To get static binding we need do nothing since it is the default. For dynamic
binding we need to apply two new keywords:
override. The keyword
virtual goes on the method at
the top of the hierarchy where the operation first appears. The derived class
versions are labeled with
override to indicate they are the
more specific version of the inherited virtual method. The implementation for
the employee hierarchy is shown below.
Calls to the
Promote method will now be dynamically bound. The
behavior is illustrated by the code below.
The thing that's so cool about all this is that the
automatically adapts its behavior to whatever type of object is passed to it.
We can even create new derived types of
Employee and pass them to
Evaluate and have their version of
This type of dynamic behavior is often called polymorphism (literally "many
shapes" or "many forms"). Polymorphism lets us write generic code such as the
Evaluate method that works correctly for all types in an
Some classes represent abstract concepts. Classic examples of this type of class include
person, student, employee, shape, fruit, mammal, etc. These are abstract concepts
because they represent only categories and not real concrete types. To model this in
code, C# provides the
abstract keyword. Any class that represents an
abstract concept should be labeled
abstract to make the intent of the
class clear to the compiler and to other programmers. In the university people
hierarchy, it is likely that the
Employee classes would all be abstract.
Marking a class
abstract prevents objects of that class from being
created. This makes sense because objects of the type do not exist in the real
world anyway since the class is modeling an abstract concept.
References are allowed even if the class is abstract. The references will be
used to refer to objects of concrete derived types.
Sometimes there is no sensible implementation for a method in a base class.
Promote method in the
Employee class provides
a good example since there is no way to promote a generic employee. A meaningful
implementation is only possible in a concrete derived class such as
is applied to a method to indicate it is an abstract operation. Abstract
methods are declaration only and cannot contain an implementation. The
signature of an abstract method is followed only by a semicolon where the
method body would normally appear.
A class containing an abstract method is considered to be incompletely specified.
For this reason, a class with an abstract method must be declared abstract or
it is a compile time error.
An abstract method acts as a specification or contract that must be fulfilled
by the derived classes. Derived classes need to provide an implementation for
the inherited abstract method or they in turn become abstract classes. The
derived class implementations are labeled with the keyword
The type of reference determines what members of an object are available.
This is generally a good thing since it limits generic code to only the
operations all types in the hierarchy share. This is a key reason why writing
generic code makes sense. This limitation can be frustrating when the derived
class offers many services beyond those of the base class. The additional
members are, of course, not available through the base reference.
To access the additional members, we need to do a type conversion to obtain a
more specific reference to the object. This type of conversion is often called
a downcast since the conversion is moving down the hierarchy from base class
to derived class. The compiler will not do these conversions automatically
since there is the possibility that the conversion may fail. To see the issue
consider the following code.
Sometimes the conversion will succeed (if the passed object is in fact a
Faculty) and sometimes it will fail (if the passed object is
Faculty). This possibility for failure is what motivates
the compiler to refuse to do the conversion implicitly and to require the
programmer to explicitly ask for it. An analogy might be the signing of a
liability waiver. The compiler forces the programmer to acknowledge the
possibility for failure and accept the risk and consequences of that failure.
The first way to do the conversion is with traditional cast syntax; that is,
by placing the destination type name inside parentheses. If the object is of
the specified type then the cast succeeds and a reference of the desired type
is obtained. However, if the object is not of the right type the CLR will
InvalidCastException. The program can handle the
exception using the
The other way to do the conversion is with the
as operator. If the
conversion succeeds then a valid reference results. If the conversion fails then
the reference will be
There is one issue that is probably obvious by this time but which might be
worth pointing out anyway. In this kind of type conversion, the thing that is
being converted is the reference and not the object. We are simply obtaining a
different reference to an existing object and the object itself is completely
It is possible to test the type of an object using operator
is. The operator
forms a Boolean expression that evaluates to true if the object is of the tested type and
to false otherwise. Note that the type of the object is being tested and not the type of
the reference. This should be obvious since the type of the reference is of course