Inheritance Fundamentals


The basic syntax for deriving a class from a base class is shown in Syntax Box 16.1. We write the class header of the derived class as usual by specifying any optional modifiers followed by the class keyword and the classname. The different and important bit in this context is the semicolon (:) followed by the name of the class we want as a base class for the derived class. The meaning of the semicolon could consequently be "is derived from."

Syntax Box 16.1 Class Definition with Optional Derivation

 Class_definition_with optional_derivation::= [<Access_modifier>] class <Derived_class_name> [ : <Base_class_name>] graphics/ccc.gif {     <Class_members_of_derived_class> } 


The colon followed by the base classname (: <Base_class_name> ) is called a class-base specification.

Example where SportsCar is derived from Car:

 class Car {     <Car_class_members> } class SportsCar : Car {     <SportsCar_class_members> } 

Listing 16.2 continues the car example introduced previously. Apart from demonstrating the basic syntax for deriving the RacingCar class (lines 26 46) from the Car class (lines 3 24), it also gives a practical view of why inheritance provides an elegant solution to the problems we encountered in Approach 1 and 2 presented earlier.

Listing 16.2 SimpleRacingCar.cs
01: using System; 02: 03: class Car 04: { 05:     private string brandName; 06: 07:     public string BrandName 08:     { 09:         get 10:         { 11:             return brandName; 12:         } 13:         set 14:         { 15:             brandName = value; 16:         } 17:     } 18: 19:     public double CalculateFuelConsumptionPerKilometer() 20:     { 21:         Console.WriteLine("Now calculating the fuel consumption"); 22:         return 0.15; 23:     } 24: } 25: 26: class RacingCar : Car 27: { 28:     private string highPressureFuelPumpSystem; 29: 30:     public string HighPressureFuelPumpSystem 31:     { 32:         get 33:         { 34:             return highPressureFuelPumpSystem; 35:         } 36:         set 37:         { 38:             highPressureFuelPumpSystem = value; 39:         } 40:     } 41:  42:     public void StartOnBoardCamera() 43:     { 44:         Console.WriteLine("Now filming and transmitting pictures"); 45:     } 46: } 47: 48: class CarTester 49: { 50:     public static void Main() 51:     { 52:         Car myGeneralCar = new Car(); 53:         RacingCar yourRacingCar = new RacingCar(); 54: 55:         myGeneralCar.BrandName = "Volvo"; 56:         Console.WriteLine("General car name: " + myGeneralCar.BrandName); 57:         Console.WriteLine("Fuel consumption of normal car: " + 58:             myGeneralCar.CalculateFuelConsumptionPerKilometer()); 59: 60:         yourRacingCar.BrandName = "Ferrari"; 61:         Console.WriteLine("\ nRacing car name: " + yourRacingCar.BrandName); 62:         Console.WriteLine("Fuel consumption of racing car: " + 63:             yourRacingCar.CalculateFuelConsumptionPerKilometer()); 64:         yourRacingCar.HighPressureFuelPumpSystem = "MaxPressureLtd"; 65:         Console.WriteLine("High pressure fuel pump system: " + 66:             yourRacingCar.HighPressureFuelPumpSystem); 67:         yourRacingCar.StartOnBoardCamera(); 68:     } 69: } General car name: Volvo Now calculating the fuel consumption Fuel consumption of normal car: 0.15 Racing car name: Ferrari Now calculating the fuel consumption Fuel consumption of racing car: 0.15 High pressure fuel pump system: MaxPressureLtd Now filming and transmitting pictures 

The Car class contains class members that are common to all cars. In this case, we have merely included the instance variable brandName (line 5), its associated property BrandName (lines 7 17), and the CalculateFuelConsumptionPerKilometer method (lines 19 23).

Only class members that are specific to the RacingCar are written as part of the RacingCar class definition. In this case, I have included the highPressureFuelPumpSystem (line 28), containing information about the high pressure fuel pumping system with which the racing car is compatible, and the associated HighPressureFuelPumpSystem property (lines 30 40). Finally, the StartOnBoardCamera method in lines 42 5 have been included.

Because a RacingCar is a Car, it must also contain the class members of a Car. But instead of physically writing these definitions inside RacingCar, we simply tell the C# compiler to include them from the Car class by writing the semicolon (:) followed by Car in line 26. Consequently, the RacingCar class contains all the class members of the Car class, plus the class members written in the RacingCar definition. The Main method of the ClassTester class is used to test this claim. First, it demonstrates the class members of the Car class (lines 55 58) through myGeneralCar of type Car declared in line 52. It then verifies with lines 60 63, and the sample output that we can, in fact, use the BrandName property (and therefore also the brandName instance variable) and the CalculateFuelConsumptionPerKilometer method as if these class members had been defined as part of the RacingCar class definition.

We didn't include the MoveForward method in this listing, because you need to understand a concept related to inheritance-called method overriding before you can understand how MoveForward can be correctly implemented in the Car and RacingCar classes. Method overriding is the subject of the next section.

Overriding Function Definitions

Recall that the MoveForward method was part of each of the three individual car classes, but was implemented differently in each of them (the FamilyCar was slow, the SportsCar was medium fast, and the RacingCar was very fast). We also concluded that MoveForward is an action taken by any car, so the MoveForward method must, according to our taxonomy-based transportation vehicle hierarchy presented earlier, be part of the Car class. This gives rise to the following (solvable) problem when we derive the RacingCar (and any of the other two subclasses) from the Car class: MoveForward will be one of the methods inherited by RacingCar. Unfortunately, the implementation of MoveForward in Car differs from what a correctly implemented MoveForward method looks like in RacingCar. Instead of adding one kilometer to the odometer, as in the Car class (for the moment we assume that this is how the MoveForward method is implemented in the Car class), we want it to add thirty kilometers in the RacingCar class (and five kilometers in the FamilyCar class and twenty kilometers in the SportsCar class). To solve our problem, we need the ability to override the Car class's implementation of the MoveForward method in each of the subclasses with an implementation that is suitable for each of the three classes. This is illustrated in Figure 16.6.

Figure 16.6. Overriding the MoveForward method of the Car class.



An abstract method does not have any implementation; instead, it forces any derived class to provide the implementation. Most programmers would argue that MoveForward should be implemented as an abstract method in the Car class, because even though it must be part of Car, we don't know exactly what it is supposed to do (we don't know the speed of a general Car). We only have a better idea of this when MoveForward exists in a more specialized class, such as SportsCar (medium fast). abstract methods and the abstract keyword are elements closely related to inheritance. These concepts are discussed in the beginning of Chapter 17, "Inheritance Part II: abstract Functions, Polymorphism, and Interfaces."

MoveForward has been implemented as a normal non-abstract method in the Car class for demonstration purposes and because we haven't yet introduced abstract methods.

Listing 16.3 demonstrates how overriding is implemented in C#.

Listing 16.3 OverridingMoveForward.cs
01: using System; 02: 03: class Car 04: { 05:     private uint odometer = 0; 06: 07:     protected uint Odometer 08:     { 09:         set 10:         { 11:             odometer = value; 12:         } 13:         get 14:         { 15:             return odometer; 16:         } 17:     } 18: 19:     public virtual void MoveForward() 20:     { 21:         Console.Write("Moving forward... "); 22:         odometer += 1; 23:         Console.WriteLine("Odometer reading: { 0} ", odometer); 24:     } 25: } 26: 27: class RacingCar : Car 28: { 29:     public override void MoveForward() 30:     { 31:         Console.Write("Moving dangerously fast forward... "); 32:         Odometer += 30; 33:         Console.WriteLine("Odometer in racing car: { 0} ", 34:             Odometer); 35:     } 36: } 37: 38: class FamilyCar : Car 39: { 40:     public override void MoveForward() 41:     { 42:         Console.Write("Moving slowly but safely forward..."); 43:         Odometer += 5; 44:         Console.WriteLine("Odometer in family car: { 0} ", 45:             Odometer); 46:     } 47: } 48: 49: class CarTester 50: { 51:     public static void Main() 52:     { 53:         RacingCar myRacingCar = new RacingCar(); 54:         FamilyCar myFamilyCar = new FamilyCar(); 55:         myRacingCar.MoveForward(); 56:         myFamilyCar.MoveForward(); 57:     } 58: } Moving dangerously fast forward... Odometer in racing car: 30 Moving slowly but safely forward...Odometer in family car: 5 

Listing 16.3 contains a Car class (lines 3 25) with a MoveForward method, which as described before advances the odometer instance variable (declared in line 5) by 1 in line 22. The RacingCar (lines 27 36) and the FamilyCar classes (lines 38 47) both contain overriding definitions (lines 29 35 and 40 46, respectively) of the MoveForward method that are suited for each of these two classes.

To allow the MoveForward method (defined in lines 19 24) of the Car class to be overridden in a derived class, it must be declared to be a virtual method, as in line 19.

If a derived class is to override a method found in its base class, the derived class must include a method with the same name as the overridden method. In our case, this is accomplished by including a method called MoveForward in the RacingCar and FamilyCar classes. Further, this overriding method definition must have the same number and types of parameters as in the base class. This is also fulfilled in the method headers of lines 29 and 40, because the number of parameters is zero, like the MoveForward method defined in the Car class.

When you derive a new class from a base class, you might, while you are writing the derived class, define a method with the same name and parameter types as a method in the base class by pure chance. In this case, your intent is not to override the method but simply to create a new method. It is important for programmers reading through your code to know whether you are creating a new method or whether you are overriding a method in the base class. This is because an overridden method in the base class has the same basic interpretation as the method that is overriding it in a sub class, but with a different implementation. On the other hand, a new method that coincidentally has the same name and parameters can be related to something completely different. For example, if we purposefully override the MoveForward method in the derived class, this method will be implemented differently but will most certainly be related to how the vehicle is moving around physically. A coincidental MoveForward might, on the other hand, deal with time and perhaps increase the age of the car instead of the odometer, or perhaps move the car forward through different design stages.

For those reasons, the compiler reminds you with a warning to state whether you are overriding or creating a new method. Consequently, if you define a method with the same name and parameter types as a method in the base class, you will see a warning. To remove this warning you need to state your intent:

  • If you are overriding the method, you must include the override keyword, as in lines 29 and 40.

  • If your name and parameter types are matching those of the base type only by coincidence, you must include the new keyword.



If you remove the override keyword in line 40, you will get the following warning:

 OverridingMoveForward.cs(39,17): warning CS0114: 'FamilyCar.MoveForward()'  hides inherited member 'Car.MoveForward()'. To make the current method override that implementation, add the override keyword. Otherwise add the  new keyword. 

The protected keyword in line 7 is an access modifier that provides the same access as the private access modifier with one difference: protected also allows access from classes that are derived from Car. In this case, we declare the Odometer property to be protected, because the overriding MoveForward methods in the derived classes need access to this property (lines 32, 34, 43, and 45), and at the same time, we are ensuring that no other classes get access to Odometer.



Some programmers would, with good reason, argue that the Odometer property should be declared public because other classes will need to get at least read-only access to the odometer instance variable.

As expected, the sample output generated by the calls to MoveForward in lines 55 and 56 confirms that we are calling two different methods, each suited for the object for which it is called.

Syntax Box 16.2 describes the formalities of overriding a base class method in a derived class. There are two obvious things you must do to make overriding work properly:

  • Include the virtual keyword in the base class method (shown first in the syntax box).

  • Include the override keyword in the overriding method of the derived class method (shown second).

However, there are a couple of other points you need to be aware of, as mentioned in the syntax box notes.

Syntax Box 16.2 Base Class Identifier

 class <Base_class_identifier> {     ...     <Access_modifier> virtual <Return_type> <Method_identifier> ([<Formal_parameter_list>] graphics/ccc.gif)     {         <Statements>     }     ... } class <Derived_class_identifier> {     ...     <Access_modifier> override <Return_type> <Method_identifier> ( graphics/ccc.gif[<Formal_parameter_list>])     {         <Statements>     }     ... } 


 <Access_modifier>::=                  public                  protected                  internal                  protected internal 


  • A virtual method cannot be private.

  • If a method in a derived class is to override a method in the base class, it must have the same name and parameter types (number and sequence of types must match) as the base class.

  • An overriding method must have the same access modifier and return type as the overridden method in the base class.

  • In addition to non-static methods, non-static properties and non-static indexers can also be declared virtual. No other function members can be declared virtual and, as a result, can never be overridden.

virtual Functions


As mentioned previously, you omit the virtual keyword to prevent a function from being overridden. You are likely to declare most of your functions to be virtual.

sealed Classes


You can prevent a whole class from being used as a base class by declaring the class to be sealed. For example, you cannot derive a class from the following class:

 sealed class MyMath {     ... } 

which makes the following class definition invalid:

 class MoreMath : MyMath {     ... } 

Three common reasons for declaring a class sealed are as follows:

  • The class is comprised of only static class members. For example, the .NET Framework's Math class is a sealed class because it only contains static class members.

  • The class design is not suitable as a base class, perhaps because its inner structure is delicate, which could easily cause derived classes to create fatal mistakes. The .NET Framework's String class belongs to this category and is, for that reason, sealed.

  • Non-virtual functions and sealed classes provide faster execution because the compiler knows more about how they will be used.


C# Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 286
Authors: Stephen Prata © 2008-2017.
If you may any questions please contact us: