2. Reusing Code with Inheritance

2.1. Extending Parent Behavior

You have learned that a child can override its parent's behavior by redefining its parent's methods. However, sometimes the child class doesn't want to replace a parent method with completely new behavior -- sometimes it wants to extend the method with additional functionality.

Consider the Car class -- it overrides Vehicle's Drive method. What if the Car's Drive method should do everything that Vehicle's drive method should do, plus some additional functionality? We could copy the code from the parent class into the child class, and add the new functionality, but that is not very desirable, because it involves code duplication. If the parent's Drive method had an undetected bug, and we copied the code into the child, the bug would be in two places, and we would have to fix it in both places.

What we need to be able to do is have the child's Drive method call the parent's Drive method, and then do the additional stuff needed for the Car's Drive method. We could try to write it like this:

     class Car : Vehicle {
        int numberOfDoors;

        // OVERRIDE Vehicle's drive() method with new behavior
        public override void Drive(int distance) {
            Drive(distance); // NOT CORRECT
            . . . do additional Car stuff . . .
        }
     }

The intent is for Car's drive method to call the drive method in the parent, but it doesn't -- it calls Car's Drive() method again. This creates an infinite loop and crashes the program. Fortunately, the fix is simple: to specify that we want to call the Drive method in the Vehicle class, we prefix the method call with base, like this:

     class Car extends Vehicle {
        int numberOfDoors;

        // OVERRIDE Vehicle's drive() method with new behavior
        public override void Drive(int distance) {
            base.Drive(distance); // calls method in parent class
            . . . do additional Car stuff . . .
        }
     }

The base keyword is a built-in variable that refers to the parent object. You don't have to use it very often, because normally you can simply use instance variables and methods defined in the parent as if they were defined in the current class. It's necessary in this case because there are two methods with the same interface defined in both classes, and you have to have a way to indicate which one you want to invoke.

2.2. Child Constructors

There is another situation when the base keyword is needed: child constructors. Look at the following code:

     class Vehicle {
        int registrationNumber;
        String owner; 

        public Vehicle() {
          registrationNumber = -1;
          owner = "** UNKNOWN **";
        }

        public Vehicle(int initRegNum, String initOwner) { 
          registrationNumber = initRegNum;
          owner = initOwner;
        }
        . . .
     }

     class Car : Vehicle {
        int numberOfDoors;

        public Car() {
          numberOfDoors = 4;
        }
        . . .
     }

Now, I need to explain how new child objects get initialized. When you create a new Car like this:

Car myCar = new Car();

C# invokes both the Car constructor and the Vehicle constructor. Thus, myCar.numberOfDoors is set to 4, myCar.registrationNumber is set to -1, and myCar.owner is "** UNKNOWN **". It does this by automatically inserting a code in the child constructor to call the parent constructor, like this:

     class Car : Vehicle {
        int numberOfDoors;

        public Car(): base() {
          numberOfDoors = 4;
        }
        . . .
     }

The code

: base()

invokes the parent constructor. Thus, when writing code in the child constructor, you can know that the instance variables defined in the class have been initialized. You can write the : base() call yourself, if you want to -- but if you don't, the compiler does it for you.

There are times when the child might want to call the parent constructor manually. For example, the Car constructor might want to invoke the second Vehicle constructor and supply its own initial values, like this:

     class Car : Vehicle {
        int numberOfDoors;

        public Car(): base(-2, "** UNKNOWN CAR OWNER **") {
          numberOfDoors = 4;
        }
        . . .
     }

In this example, new Car objects have an initial registration number of -2, and an owner "** UNKNOWN CAR OWNER **".

There are also times when the child must call the parent constructor manually. If the parent defines one or more constructors, and none of them are parameterless, then the child must use base() to call the appropriate parent constructor. For example, if Vehicle had only the second constructor (not the parameterless one), then the Car constructor would have to explicitly invoke the Vehicle constructor with parameters.