1. Inheritance Basics

The term inheritance refers to the fact that one class can inherit methods and instance variables from another class. The class that does the inheriting is said to be a child of the class from which it inherits. If class B is a child of class A, we also say that class A is a parent of class B. (Sometimes the terms "superclass" and "subclass" are used instead of parent and child.) A child class can add instance variables and methods to those that it inherits. It can also replace or modify inherited methods (though not inherited variables). The inheritance relationship is sometimes shown by a diagram in which the child is shown connected to its parent, like this.

In C#, when you create a new class, you can declare that it is a child of an existing class. If you are defining a new class named "B" and you want it to be a child of an existing class named "A", you would write

        class B : A {
            .
            .  // additions to, and modifications of,
            .  // stuff inherited from class A
            .
        }


The notation "B : A" means "create a new class named B that extends the definition of an existing class A".

If you do not specify a parent class when defining a new class, your class inherits from the System.Object class. That's right -- the classes defined in the last couple of chapters were involved in an inheritance relationship, even though you didn't know it. I'll talk more about the Object class later in this chapter.

Several classes can be declared as children of the same parent. The children, which might be referred to as "sibling classes," share some instance variables and methods -- namely, the ones they inherit from their common parent. The parent class defines these shared instance variables and methods. In the diagram below, classes StreamReader and TextReader are sibling classes. Inheritance can also extend over several "generations" of classes, forming a hierarchy of classes. This is shown in the diagram, where class StreamReader is a child of class TextReader which is itself a child of class MarshalByRefObject. In this case, class StreamReader is considered to be a descendant of class MarshalByRefObject, inheriting methods and variables defined in MarshalByRefObject, even though it is not a direct descendant.

1.1. Inheritance in the C# API

As you may have guessed from the example, inheritance is used extensively in the C# class libraries. For example, have a look at the documentation for the StreamReader class. Here is the first part of the documentation showing the inheritance details:

The diagram indicates that, in turn, StreamReader inherits from TextReader, which inherits from MarshalByRefObject, which inherits from Object.

1.2. An Inheritance Example

Let's look at an example. Suppose that a program has to deal with motor vehicles, including cars, trucks, and motorcycles. (This might be a program used by a Department of Motor Vehicles to keep track of registrations.) The program could use a class named Vehicle to represent all types of vehicles. The Vehicle class could include instance variables such as registrationNumber and owner and instance methods such as TransferOwnership(). These are variables and methods common to all vehicles. Three subclasses of Vehicle -- Car, Truck, and Motorcycle -- could then be used to hold variables and methods specific to particular types of vehicles. The Car class might add an instance variable numberOfDoors, the Truck class might have numberOfAxels, and the Motorcycle class could have a boolean variable hasSidecar. (Well, it could in theory at least, even if it might give a chuckle to the people at the Department of Motor Vehicles.) The declarations of these classes in C# program would look, in outline, like this:

     class Vehicle {
        int registrationNumber;
        string owner; 
        public void Drive(int distance) {
            . . .
        }
        . . .
     }
     class Car : Vehicle {
        int numberOfDoors;
        . . .
        public void GetDoors() { ... }
     }
     class Truck : Vehicle {
        int numberOfAxles;
        . . .
     }
     class Motorcycle : Vehicle {
        bool hasSidecar;
        . . .
     }

Suppose that myCar is a variable of type Car that has been declared and initialized with the statement

Car myCar = new Car();

Given this declaration, a program could refer to myCar.GetDoors(), since GetDoors() is a method in the class Car. But since class Car extends class Vehicle, a car also has all the instance variables and methods of a vehicle. This means that myCar.registrationNumber, myCar.owner, and myCar.drive() also exist.

1.3. Overriding Behavior

In some situations, a child class needs to replace or extend existing methods inherited from its parent.

In the example above, the Vehicle class defined a Drive( ) method. Let's say that the Car class needs to be able to replace the functionality of the Drive() method with something more appropriate for Car instances. You might try something like this:

     class Car : Vehicle {
        int numberOfDoors;

        // OVERRIDE Vehicle's Drive() method with new behavior
        void Drive(int distance) {
            . . .
        }
     }

However, child classes are not allowed to redefine methods inherited from their parent unless

  1. The parent's method is marked virtual

  2. The child's method is marked override, and has exactly the same visibility modifier, return type, and method parameters as the parent's method

So, the code would look like this:

     class Vehicle {
        int registrationNumber;
        string owner; 

        // a method that can be overridden by child classes
        public virtual void Drive(int distance) {
            . . .
        }
        . . .
     }

     class Car : Vehicle {
        int numberOfDoors;

        // OVERRIDE Vehicle's Drive() method with new behavior
        public override void Drive(int distance) {
            . . .
        }
     }

When a child class redefines a method defined in the parent class using this approach, we say that the new method overrides the parent's method. That's because, in the following code, when myCar.Drive(3) is called, the code in the Car class, not the Vehicle class, executes:

Car myCar = new Car();
myCar.Drive(3);

1.4. The "is-a" test

Inheritance is often used when programmers want to create a class that is similar to an existing class, but has extra capabilities. It's a great way to reuse existing code: instead of copying code from one class into another, you can use inheritance to get the desired functionality in the new class without the problems of code duplication.

However, inheritance is not appropriate in all situations where code reuse is desired. Inheritance is only appropriate when the child class is a more specific kind of thing represented by the parent class. For instance, in the Vehicle example above, cars, trucks, and motorcycles are all specific kinds of vehicles. In technical terms, they are specializations ("more specific kinds") of a vehicle.

To determine whether inheritance is appropriate for two candidate classes A (a proposed child) and B (the proposed parent), try to say the sentence "an A is a kind of B," or more briefly, "an A is a B." For example, it's ok to say "a Car is a kind of Vehicle," but you wouldn't say "a Vehicle is a kind of Car." If the sentence doesn't make sense, don't use inheritance.

1.5. Information Hiding and Inheritance

You learned in chapter 9 that a class can enforce information hiding by marking its instance variables private. That keeps code outside the class from being able to access data inside the class. This can create a problem for child classes that need to access instance variables defined in the parent. Consider this example:

     class Vehicle {
        private int registrationNumber;
        private string owner; 

        public virtual void Drive(int distance) {
            . . .
        }
        . . .
     }
     class Car : Vehicle {
        private int numberOfDoors;

        public void ShowInfo() {
          Console.WriteLine(registrationNumber + ": " + owner); // WON'T WORK
        }
        . . .
     }

Since Vehicle's instance variables are marked private, code in the Car class is not permitted to access them. The compiler will report an error. To solve this problem, mark Vehicle's instance variables protected instead of private, like this:

     class Vehicle {
        protected int registrationNumber;
        protected String owner; 

        . . .
     }

The protected visibility modifier allows code in child classes to access the instance variables, while preventing code in other classes from accessing the data directly.