Learning C# and OOP, Inheritance

Baldwin shows you how to extend an existing class to create a new class.  The new class is the blueprint for a new type.  This is the mechanism for class inheritance in C#.  Inheritance provides a formal mechanism for code reuse.

Published:  November 3, 2002
By Richard G. Baldwin

C# Programming Notes # 110


Preface

This lesson is one of a series of lessons designed to teach you about the essence of Object-Oriented Programming (OOP) using C#.

The first lesson in the group was entitled Learning C# and OOP, Getting Started, Objects and Encapsulation.  That lesson, and each of the lessons following that one, has provided explanations of certain aspects of the essence of Object-Oriented Programming using C#.   The previous lesson was entitled Learning C# and OOP, Indexers, Part 2.

Essential aspects of OOP in C#

This miniseries will describe and discuss the necessary and significant aspects of OOP using C#.

I will provide that information in a relatively high-level format, devoid of any requirement to know detailed C# syntax.  In those cases where an understanding of C# syntax is required, I will provide the necessary syntax information in the form of sidebars or other obvious formats.

Therefore, if you have a general understanding of computer programming, you should be able to read and understand the lessons in this miniseries, even if you don't have a strong background in the C# programming language.

Viewing tip

You may find it useful to open another copy of this lesson in a separate browser window.  That will make it easier for you to scroll back and forth among the different listings while you are reading about them.

Supplementary material

I recommend that you also study the other lessons in my extensive collection of online programming tutorials.  You will find a consolidated index at www.DickBaldwin.com.

Preview

Extending a class

This lesson shows you how to extend an existing class to create a new class.  The new class is the blueprint for a new type.

Inheritance and code reuse

The existing class is often called the superclass (or base class) and the new class is often called the subclass (or derived class).  This is the mechanism for class inheritance in C#.  Inheritance provides a formal mechanism for code reuse.

Classes form a hierarchy.  The subclass inherits all of the variables and all of the methods defined in the superclass, and its superclass, etc.

Car radios with tape players

A class from a previous lesson (whose objects represent car radios) is extended in this lesson to define a new class, (whose objects represent extended car radios that contain tape players).

Sending messages to the object

Objects of the new class know how to respond to messages for inserting, playing, and removing a tape, in addition to those messages appropriate for objects of the original Radio class.

Method overriding

Method overriding is used to modify the behavior of an inherited method of the Radio class named playStation.  This causes the playStation method to behave appropriately when a tape has been inserted into the tape player.

Discussion and Sample Code

The three pillars of OOP

In an earlier lesson, I explained that most books on OOP will tell you that in order to understand OOP, you must understand the following three concepts:

  • Encapsulation
  • Inheritance
  • Polymorphism

Encapsulation

Previous lessons in this series provided an explanation of encapsulation.

Inheritance

This lesson will provide an explanation of inheritance.  I will use a simple program to explain the concept of inheritance.

Polymorphism

Polymorphism is the most complex of the three pillars, and will be fully explained in future lessons.  However, it will be necessary to touch on polymorphism based on method overriding in this lesson as well.

A new data type

Whenever you define a class in C#, you cause a new data type to become available to the program.  Therefore, whenever you need a new data type, you can define a new class to make that type available.

Creating a new type

Defining a new class (to create a new type) can involve a lot of effort.  Sometimes you have an option that can greatly reduce the effort required to create your new type.

Extending a class

If a class (type) already exists that is close to what you need, you can often extend that class to produce a new class that meets your needs. 

In many cases, this will require much less effort than the effort required to start from scratch and define a new class to establish a new type.

The ability to extend one class into another new class is the essence of inheritance.

According to the current jargon, the new class is called the subclass and the class that was extended is called the superclass.

What is inherited?

When you define a class that extends another class, an object instantiated from your new class will contain all of the methods and all of the variables defined in your new class.  The object will also contain all of the methods and all of the variables defined in all of the superclasses of your new class.

Code reuse

Therefore, inheritance often makes it possible to define a new class with a minimum requirement to write new code by formally reusing the code that was previously written into the superclasses.

The behavior of the methods

The behavior of the methods defined in a superclass and inherited into your new class may, or may not, be appropriate for an object instantiated from your new class.  If those methods are appropriate, you can simply leave them alone.

Overriding to change behavior

If the behavior of one or more methods defined in a superclass and inherited into your new class is not appropriate for an object of your new class, you can change that behavior by overriding the method in your new class.

How do you override a method?

To override a method in your new class, simply reproduce the name, argument list, and return type of the original method in a new method definition in your new class.  Then provide a body for the new method.  Write code in that body to cause the behavior of the overridden method to be appropriate for an object of your new class.

Any method that is declared virtual in the superclass can be overridden in a subclass.

Overriding versus overloading

Don't confuse method overriding with method overloading

An overloaded method differs from the original method in the number and/or type of its arguments.  A different return type is permitted for an overloaded method, but is not sufficient by itself to distinguish an overloaded method from the original.

I will have much more to say about overloaded methods in a future lesson.

Car radios with built-in tape players

This lesson presents a sample program that duplicates the functionality of the program named Radio01a discussed in a previous lesson.  A class named Radio is used to define the specifics of objects intended to simulate car radios.

A class named Combo extends the Radio class to define the specifics of objects intended to simulate extended car radios having built-in tape players.

The Radio class

A previous program defined a class named Radio.  Objects instantiated from the Radio class (see the previous lessons for a discussion of instantiating objects) were intended to simulate car radios. (Note that the car radios simulated by objects of the Radio class didn't have built-in tape players.)

The Combo class

In this lesson, I will use inheritance to extend the Radio class into a new class named Combo.  Objects instantiated from the Combo class are intended to simulate car radios with a built-in tape player.

A complete listing of the new program is shown in Listing 8 near the end of the lesson.

Overridden playStation method

In particular, a method named playStation, defined in the superclass named Radio, is overridden in the subclass named Combo.

The original version of the playStation method in the superclass supports only radio operations.  (It doesn't support tape operations.)

The overridden version of the playStation method defined in the subclass supports both radio operations and tape operations.

(Using current jargon, the behavior of the version of the playStation method defined in the Radio class is not appropriate for an object of the Combo class.  Therefore, the method was overridden in the Combo class to cause its behavior to be appropriate for objects instantiated from the Combo class.)

Will discuss in fragments

As usual, I will discuss this program in fragments.  I will begin my discussion with the definition of the new class named Combo.  Then I will come back and discuss the class named Radio and the driver class named Radio03.

The combo class

The code fragment in Listing 1 shows the beginning of the class named Combo. (Note that the boldface was added for emphasis only.  C# source code does not use boldface, Italics, or other such typographical features.)
 
class Combo : Radio{
  
  private bool tapeIn = false;
  
  public Combo(){//constructor
    System.Console.WriteLine(
           "Combo object constructed");
  }//end constructor

Listing 1

Two new items

There are two new concepts illustrated in Listing 1 that you did not see in the code in the previous lessons.

Combo extends Radio

First, the class named Combo extends or inherits from the class named Radio

(The syntax for causing a class to extend another class in C# is to join the name of the new class and the name of the class being extended with a colon, as shown in the first line of Listing 1.)

What does this mean?

This means that an object instantiated from the Combo class will contain all of the variables and all the methods defined in the Combo class, plus all the variables and methods defined in the Radio class, and its superclasses. (The variables and methods of the superclass are inherited into the subclass.)

An explicit constructor

The second new concept illustrated in Listing 1 is that the class named Combo defines an explicit constructor.

Defining a constructor is optional

When defining a new class, it is not necessary to define a constructor.  If you don't define a constructor, a default constructor will be provided automatically.  That was the case for the programs in the previous lessons in this miniseries.

Why define a constructor?

The intended purpose of a constructor is to initialize the instance variables belonging to the new object.

However, constructors can do other things as well.  In this case, I used an explicit constructor to display a message when an object is instantiated from the class named Combo.

(I really just wanted to show you how to define an explicit constructor.  There was no special initialization that needed to be handled in this case.)

Brief discussion of constructors

I'm not going to discuss constructors in detail at this point.  However, I will give you a few rules regarding constructors.

  • Constructors (like methods) can be overloaded (I will explain in detail what overloading means in a subsequent lesson).
  • The names of constructors must match the names of the classes in which they are defined.
  • A constructor declaration doesn't indicate a return type (such as void or int).

A boolean instance variable

Although not necessarily new to this lesson, the code in Listing 1 also declares and initializes an instance variable of type bool named tapeIn.  This instance variable will be used later to deal with the logic of inserting tapes, removing tapes, etc.

Instance methods

The new class named Combo defines three instance methods, each of which has to do with the handling of tape in the tape player:

  • insertTape
  • removeTape
  • playTape

(If you feel ambitious, you could upgrade this class even further to add features such as rewind, fast forward, pause, etc.)

In addition, the Combo class provides an overridden version of the playStation method, which it inherits from the Radio class.

The insertTape method

The entire method named insertTape is shown in Listing 2.  This is the method that is used to simulate the insertion of a tape by the user.
 
  public void insertTape(){
    System.Console.WriteLine(
                        "Insert Tape");
    tapeIn = true;
    System.Console.WriteLine(
                       "  Tape is in");
    System.Console.WriteLine(
                     "  Radio is off");
  }//end insertTape method

Listing 2

The most significant thing about the code in Listing 2 is the assignment of the true value to the boolean variable named tapeIn. (The variable named tapeIn is declared and initialized in Listing 1.)

Other than setting the value of the tapeIn variable to true, the code in Listing 2 simply prints some messages to indicate what is going on.

What is tapeIn used for?

As you will see shortly, the value of the variable named tapeIn is used to determine when it is possible to play the tape and when it is possible to play the radio.  (It is not possible to play both the tape and the radio simultaneously.)

According to the logic:

  • If tapeIn is true, it is possible to play the tape but it is not possible to play the radio.
  • If tapeIn is false, it is possible to play the radio, but it is not possible to play the tape.

The removeTape method

The removeTape method of the Combo class is shown in Listing 3.  Its behavior is pretty much the reverse of the insertTape method, so I won't discuss it further.
 
  public void removeTape(){
    System.Console.WriteLine(
                        "Remove Tape");
    tapeIn = false;
    System.Console.WriteLine(
                      "  Tape is out");
    System.Console.WriteLine(
                      "  Radio is on");
  }//end removeTape method

Listing 3

The playTape method

Listing 4 shows the method named playTape defined in the new Combo class.
 
  public void playTape(){
    System.Console.WriteLine(
                          "Play Tape");
    if(!tapeIn){
      System.Console.WriteLine(
            "  Insert the tape first");
    }else{
      System.Console.WriteLine(
                  "  Tape is playing");
    }//end if/else
  }//end playTape

Listing 4

Confirm that the tape is ready

The invocation of the method named playTape can be thought of as sending a message to the Combo object to play the tape. 

The code in the playTape method checks to confirm that the value stored in the tapeIn variable is true before implementing the incoming message to play the tape.

If tapeIn is false, an error message is displayed advising the user to insert the tape first.

If tapeIn is true, the method prints a message indicating that the tape is playing.

The overridden playStation method

The Combo class overrides the playStation method, which it inherits from the Radio class.  The overridden version of the playStation method is shown in Listing 5.

As you will see when we examine the Radio class, this version of the method duplicates the signature of the playStation method in the superclass named Radio, but provides a different body.
 
  public override void playStation(
                            int index){
    System.Console.WriteLine(
                         "Play Radio");
    if(!tapeIn){
      System.Console.WriteLine(
          "  Playing the station at " 
               + stationNumber[index]
               + " Mhz");
    }else{
      System.Console.WriteLine(
            "  Remove the tape first");
    }//end if/else
  }//end method playStation

Listing 5

An override must be declared

This version of the playStation method also declares that the method overrides the inherited version of the method, which must be declared virtual.

(When we examine the Radio class, we will see that the version of the playStation method that is defined in that class is declared virtual.  Like C++, and unlike Java, this is a requirement for overriding methods in C#.)

Aware of the tape system

The original version of the playStation method in the Radio class is unaware of the existence of the tape system.  However, this overridden version of the method is aware of the existence of the tape system and behaves accordingly. 

Depending on the value of the variable named tapeIn, this method will either tune and play a radio station, or display a message instructing the user to remove the tape.

Which version of playStation is executed?

When the playStation method is invoked on an object of the Radio class, the version of the playStation method defined in the Radio class will be executed.

When the playStation method is invoked on an object of the Combo class, the overridden version of the method defined in the Combo class (and not the original version defined in the Radio class) will be executed.

Radio class

Listing 5 shows the definition of the Radio class.  I provide it here for easy referral.  This class definition is unchanged from the version that you saw in a previous lesson.
 
class Radio{
  //This class simulates the plans from
  // which the radio object is created.
  protected double[] stationNumber = 
                         new double[5];

  //Define a property indexer
  public double this[int i]{
    //Define the set block of a 
    // property indexer
    set{  
      this.stationNumber[i] = value;
    }//end set
    
    //Define the get block of the 
    // property indexer to illustrate
    // the syntax.
    get{
      return this.stationNumber[i];
    }//end get
  }//end this indexer
  
  //This version of playStation doesn't
  // accommodate tape operations.
  public virtual void playStation(
                            int index){
    System.Console.WriteLine(
            "Playing the station at " 
               + stationNumber[index]
               + " Mhz");
  }//end method playStation
  
}//end class Radio

Listing 6

Combo overrides playStation

As you saw earlier, the class named Combo extends this class and overrides the method named playStation, shown in boldface in Listing 6.

Doesn't support tape operations

If you examine the code for the playStation method in the Radio class in Listing 6, you will see that it assumes radio operations only and doesn't support tape operations.  That is the reason that it needs to be overridden.

(For example, it doesn't know that it should refuse to play a radio station when a tape is being played.)

Must be virtual for override

You should also note that the playStation method defined in the Radio class is declared virtual

As mentioned earlier, in C#, only virtual methods can be overridden.

So, what's the big deal with inheritance?

The use of inheritance eliminated the need for me to define a new class that reproduces all of the code in the class named Radio.

I was able to define a new class for an improved radio, (which supports a tape player), simply by extending the Radio class and overriding one of its methods.

There are lots of reasons for using inheritance, and this is only one of them.

The driver class

The new driver class named Radio03 is shown in Listing 7.
 
public class Radio03 : Object{
  //This class simulates the 
  // manufacturer and the human user
  public static void Main(){
    //This is the manufacturer of the
    // radio
    Combo myObjRef = new Combo();
    
    //This is the user of the radio
    //Invoke an indexer set block
    myObjRef[3] = 93.5;
    //Now perform some operations    
    myObjRef.playStation(3);
    myObjRef.insertTape();
    myObjRef.playStation(3);
    myObjRef.removeTape();
    myObjRef.playStation(3);
    myObjRef.playTape();
    myObjRef.insertTape();
    myObjRef.playTape();
    myObjRef.removeTape();
    myObjRef.playStation(3);
  }//end main
}//end class Radio03

Listing 7

New object of the Combo class

The most significant change in this class (relative to the driver class named Radio01a in a previous lesson) is the single statement in boldface Italics.  Note that unlike the previous program, this program instantiates a new object of the Combo class (instead of the Radio class).

All of the other new code in Listing 8 is used to send messages to the new object in order to exercise its behavior.

Invoke the playStation method

I present this code here to emphasize that this code instantiates a new object of the Combo class.  This assures that the overridden version of the method named playStation will be executed by the boldface statements in Listing 7 that invoke the playStation method.

(Although it is not the case in Listing 7, even if the reference to the object of type Combo had been stored in a reference variable of type Radio, instead of a reference variable of type Combo, invocation of the playStation method on that reference would have caused the overridden version of the method to be executed.  That is the essence of runtime polymorphism based on overridden methods in C#.)

Program output

That object responds to those messages by producing the following output on the computer screen:

Combo object constructed
Play Radio
  Playing the station at 93.5 Mhz
Insert Tape
  Tape is in
  Radio is off
Play Radio
  Remove the tape first
Remove Tape
  Tape is out
  Radio is on
Play Radio
  Playing the station at 93.5 Mhz
Play Tape
  Insert the tape first
Insert Tape
  Tape is in
  Radio is off
Play Tape
  Tape is playing
Remove Tape
  Tape is out
  Radio is on
Play Radio
  Playing the station at 93.5 Mhz

Leave as an exercise for the student

As the old saying goes, I will leave it as an exercise for the student to correlate the messages in Listing 7 with the output shown above.

Summary

Inheritance provides a formal mechanism for code reuse.

Extending an existing class often provides an easy way to create a new type.  This is primarily true when an existing class creates a type whose features are close to, but not identical to the features needed in the new type.

When an existing class is extended to define a new class, the existing class is often called the superclass and the new class is often called the subclass.

The subclass inherits all of the variables and all of the methods defined in the superclass and its superclasses.

The behavior of methods inherited into the subclass may or may not be appropriate for an object instantiated from the subclass.  If they are not appropriate, you can change that behavior by overriding the methods in the definition of the subclass (provided that they were originally declared virtual in the superclass).

To override a method in the subclass, reproduce the name, argument list, and return type of the original method in a new method definition in the subclass.

Provide a body for the overridden method, causing the behavior of the overridden method to be appropriate for an object of the subclass.

Any method that is declared virtual can be overridden in a subclass.

The program discussed in this lesson extends a Radio class to produce a subclass that simulates an upgraded car radio containing a tape player.  Method overriding is used to modify the behavior of an inherited method named playStation to cause that method to behave appropriately when a tape has been inserted into the radio.  This is often referred to as runtime polymorphism

Method overriding is different from method overloading.  Method overloading will be discussed in the next lesson.

What's Next?

In the next lesson, I will explain the use of overloaded methods for the purpose of achieving compile-time polymorphism.

Complete Program Listing

A complete listing of the program is shown in Listing 8 below.

The primary difference between this program and the program in the earlier lesson (whose objects simulate car radios) is the inclusion in this program of a new class named Combo.  The class named Combo extends the original Radio class to create a new type of radio that also contains a tape player.
 
/*File Radio03.cs
Copyright 2002, R.G.Baldwin
Simulates the manufacture and use of a 
combination car radio and tape player.

Illustrates extending a class and
overriding a virtual method.

This program produces the following
output on the computer screen:
  
Combo object constructed
Play Radio
  Playing the station at 93.5 Mhz
Insert Tape
  Tape is in
  Radio is off
Play Radio
  Remove the tape first
Remove Tape
  Tape is out
  Radio is on
Play Radio
  Playing the station at 93.5 Mhz
Play Tape
  Insert the tape first
Insert Tape
  Tape is in
  Radio is off
Play Tape
  Tape is playing
Remove Tape
  Tape is out
  Radio is on
Play Radio
  Playing the station at 93.5 Mhz
**************************************/
using System;

public class Radio03 : Object{
  //This class simulates the 
  // manufacturer and the human user
  public static void Main(){
    //This is the manufacturer of the
    // radio
    Combo myObjRef = new Combo();
    
    //This is the user of the radio
    //Invoke an indexer set block
    myObjRef[3] = 93.5;
    //Now perform some operations    
    myObjRef.playStation(3);
    myObjRef.insertTape();
    myObjRef.playStation(3);
    myObjRef.removeTape();
    myObjRef.playStation(3);
    myObjRef.playTape();
    myObjRef.insertTape();
    myObjRef.playTape();
    myObjRef.removeTape();
    myObjRef.playStation(3);
  }//end main
}//end class Radio03
//===================================//

class Radio{
  //This class simulates the plans from
  // which the radio object is created.
  // This code is the same as in the
  // program named Radio01a.
  protected double[] stationNumber = 
                         new double[5];

  //Define a property indexer
  public double this[int i]{
    //Define the set block of a 
    // property indexer
    set{  
      this.stationNumber[i] = value;
    }//end set
    
    //Define the get block of the 
    // property indexer to illustrate
    // the syntax.
    get{
      return this.stationNumber[i];
    }//end get
  }//end this indexer
  
  //This version of playStation doesn't
  // accommodate tape operations.
  public virtual void playStation(
                            int index){
    System.Console.WriteLine(
            "Playing the station at " 
               + stationNumber[index]
               + " Mhz");
  }//end method playStation
  
}//end class Radio
//===================================//

class Combo : Radio{
  
  private bool tapeIn = false;
  //---------------------------------//
  
  public Combo(){//constructor
    System.Console.WriteLine(
           "Combo object constructed");
  }//end constructor
  //---------------------------------//
  
  //Overridden playStation method. This
  // overridden version accommodates
  // tape operations.
  public override void playStation(
                            int index){
    System.Console.WriteLine(
                         "Play Radio");
    if(!tapeIn){
      System.Console.WriteLine(
          "  Playing the station at " 
               + stationNumber[index]
               + " Mhz");
    }else{
      System.Console.WriteLine(
            "  Remove the tape first");
    }//end if/else
  }//end method playStation
  //---------------------------------//
  
  public void insertTape(){
    System.Console.WriteLine(
                        "Insert Tape");
    tapeIn = true;
    System.Console.WriteLine(
                       "  Tape is in");
    System.Console.WriteLine(
                     "  Radio is off");
  }//end insertTape method
  //---------------------------------//
  
  public void removeTape(){
    System.Console.WriteLine(
                        "Remove Tape");
    tapeIn = false;
    System.Console.WriteLine(
                      "  Tape is out");
    System.Console.WriteLine(
                      "  Radio is on");
  }//end removeTape method
  //---------------------------------//
  
  public void playTape(){
    System.Console.WriteLine(
                          "Play Tape");
    if(!tapeIn){
      System.Console.WriteLine(
            "  Insert the tape first");
    }else{
      System.Console.WriteLine(
                  "  Tape is playing");
    }//end if/else
  }//end playTape
}//end class combo

Listing 8


Copyright 2002, Richard G. Baldwin.  Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.

About the author

Richard Baldwin is a college professor (at Austin Community College in Austin, Texas) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas.  He is the author of Baldwin's Programming Tutorials, which has gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.

Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.

baldwin@DickBaldwin.com

-end-


© 1996, 1997, 1998, 1999, 2000, 2001, 2002 Richard G. Baldwin