More about Inheritance

for RuBoard

Our case study has illustrated many important features of object-oriented programming, but there is more to the story. Methods in a derived class may hide the corresponding method in the base class, possibly making use of the base class method in their implementation. Alternatively, the base class may have virtual methods , which are not bound to an object at compile time but are bound dynamically at runtime. A derived class may override a virtual method. This dynamic behavior enables polymorphic code, which is general code that applies to classes in a hierarchy, and the specific class that determines the behavior is determined at runtime.

C# provides keywords virtual and override that precisely specify in base and derived classes, respectively, that the programmer is depending on dynamic binding. By providing a mechanism to specify polymorphic behavior in the language, C# helps programs deal with an issue known as the fragile base class problem, which can result in unexpected behavior in a program when a base class in a library is modified but the program itself is unchanged.

Employee Class Hierarchy

In this section we will use a much simpler class hierarchy to illustrate the important concepts. The base class is Employee , which has a public field Name . There are two derived classes. The SalaryEmployee class has a salary field. The WageEmployee class has fields for an hourly rate of pay and for the number of hours worked. Figure 4-3 illustrates this simple class hierarchy.

Figure 4-3. Employee class hierarchy.

graphics/04fig03.gif

Method Hiding

A derived class inherits the methods of its base class, and these inherited methods are automatically available "as is." Sometimes we may want the derived class to do something a little different for some of the methods of the base class. In this case we will put code for these changed methods in the derived class, and we say the derived class "hides" the corresponding methods in the base class. Note that hiding a method requires that the signatures match exactly. (As we discussed in Chapter 3, methods have the same signature if they have the same number of parameters, and these parameters have the same types and modifiers, such as ref or out . The return type does not contribute to defining the signature of a method.)

In C#, if you declare a method in a derived class that has the same signature as a method in the base class, you will get a compiler warning message. In such a circumstance, there are two things you may wish to do. The first is to hide the base class method, which is what we discuss in this section. The second is to override the base class method, which we will discuss in the next section.

To hide a base class method, place the keyword new in front of the method in the derived class. When you hide a method of the base class, you may want to call the base class method within your implementation of the new method. You can do this by using the keyword base , followed by a period, followed by the method name and actual parameters.

The example program HideEmployee illustrates method hiding. This program has the Employee base class and the SalaryEmployee derived class. Each class has a Show method. The derived class's Show method hides the Show method of the base class. But the derived class can call the base class Show method through the base keyword. Here is the code:

 // Employee.cs  using System;  public class Employee  {     public string Name;     public Employee(string name)     {        Name = name;     }     public void Show()     {        Console.WriteLine("name = {0}", Name);     }  }  public class SalaryEmployee : Employee  {     private decimal salary;     public SalaryEmployee(string name,                           decimal salary)        : base(name)     {        this.salary = salary;     }  new  public void Show()     {  base  .Show();        Console.WriteLine(           "salary = {0:C}", salary);     }  } 

If you delete the new in the derived class Show method, you will get a compiler warning message:

 warning CS0108: The keyword new is required on  'SalaryEmployee.Show()' because it hides  inherited member 'Employee.Show()' 

Static Binding

In C# the normal way methods are tied to classes is through static binding . That means the object reference type is used at compile time to determine the class whose method is called. The HideEmployee program we just looked at illustrates static binding, using a simple Employee class and a derived SalaryEmployee class. Here is the test program:

 // TestEmployee.cs  using System;  public class TestEmployee  {     public static void Main(string[] args)     {        Employee emp = new Employee("Ellen");        SalaryEmployee sal =           new SalaryEmployee("Sally", 100m);        emp.Show();        sal.Show();        //sal = emp;        emp = sal;        emp.Show();     }  } 

In this program emp is an object reference of type Employee . Calling Show through this object reference will always result in Employee.Show being called, no matter what kind of object emp may actually be referring to. Here is the output. Notice that the second time we call Show through emp we are still getting the Employee version of Show (only the name is displayed).

 name = Ellen  name = Sally  salary = 0.00  name = Sally  Press any key to continue 
Type Conversions in Inheritance

This program also illustrates another feature of inheritance, type conversions. After the objects emp and sal have been instantiated , the object references will be referring to different objects, one of type Employee and the other of type SalaryEmployee . Note that the SalaryEmployee object has an additional field, salary .

The test program tries two type conversions:

 //sal = emp;  emp = sal; 

The first assignment is illegal (as you can verify by uncommenting and trying to compile). Suppose the assignment were allowed. Then you would have an object reference of type SalaryEmployee referring to an Employee object. If the conversion "down the hierarchy" (from a base class to a derived class) were allowed, the program would be open to a bad failure at runtime. What would happen if the code tried to access a nonexistent member, such as sal accessing the member salary?

The opposite assignment:

 emp = sal; 

is perfectly legal. We are converting "up the hierarchy." This is okay because of the IS-A relationship of inheritance. A salary employee "is" an employee. It is a special kind of employee. Everything that applies to an employee also applies to a salary employee. There is no "extra field" in the Employee class that is not also present in the SalaryEmployee class.

Virtual Methods

In C# you can specify that a method in C# will be bound dynamically . Only at runtime will it be determined whether the base or derived class's method will be called. The program VirtualEmployee illustrates this behavior. The file VirtualEmployee.cs contains class definitions for a base class and a derived class, as before. But this time the Show method is declared as virtual in the base class. In the derived class the Show method is declared override (in place of new that we used before with method hiding). Now the Show method in the derived class does not hide the base class method but overrides it.

 // VirtualEmployee.cs  using System;  public class Employee  {     public string Name;     public Employee(string name)     {        Name = name;     }  virtual  public void Show()     {        Console.WriteLine("name = {0}", Name);     }  }  public class SalaryEmployee : Employee  {     private decimal salary;     public SalaryEmployee(string name,                           decimal salary)        : base(name)     {        this.salary = salary;     }  override  public void Show()     {        base.Show();        Console.WriteLine(           "salary = {0:C}", salary);     }  } 

We use the same test program. Here is the output. Now, the second time we call Show through sal , we will be getting the SalaryEmployee.Show method, showing the salary as well as the name.

 name = Ellen  name = Sally  salary = 0.00  name = Sally   salary = 0.00  Press any key to continue 
Virtual Methods and Efficiency

Virtual method invocation (dynamic binding) is slightly less efficient than calling an ordinary nonvirtual method (static binding). With a virtual method call, there is some overhead at runtime associated with determining which class's method will be invoked. C# allows you to specify in a base class whether you want the flexibility of a virtual method or the slightly greater efficiency of a nonvirtual method. You simply decide whether or not to use the keyword virtual . (In some languages all methods are virtual, and you don't have this choice.)

Method Overriding

The override keyword in C# is very useful for making programs clearer. In some languages, such as C++, there is no special notation for overriding a method in a derived class. You simply declare a method with the same signature as a method in the base class. If the base class method is virtual, the behavior is to override. If the base class method is not virtual, the behavior is to hide. In C# this behavior is made explicit.

The Fragile Base Class Problem

One subtle pitfall in object-oriented programming is the fragile base class problem. Suppose the override keyword syntax did not exist. Suppose further that you derive a class from a third-party class library, and you have a method in the derived class that does not hide or override any method in the base class.

Now a new version of the class library comes out, and the base class has a new virtual method whose signature happens to match one of the methods in your class. Now you can be in trouble! Classes that derive from your class may now behave in unexpected ways. Code that was "expected" to call the new method in the class library ”or in code in a derived class that deliberately overrides this method ”may now call your method that has nothing whatever to do with the method in the class library.

This situation is rare, but if it occurs it can be extremely vicious. Fortunately, C# helps you avoid such situations by requiring you to use the override keyword if you are indeed going to perform an override. If you do not specify either override or new and a method in your derived class has the same signature as a method in a base class, you will get a compiler error or warning. Thus, if you build against a new version of the class library that introduces an accidental signature match with one of your methods, you will get warned by the compiler.

COM and the Fragile Base Class Problem

There is no inheritance in Microsoft's Component Object Model (COM). Microsoft used the fragile base class problem as a rationale for not providing inheritance. The issue is much more important for binary components , such as COM objects, than for traditional class libraries distributed in source code, because if the problem arises and you have no source for the library, your options are limited. The real killer is for the problem not to reveal itself in the development lab, but to crop up only in the field after the application has been deployed.

Microsoft .NET has similar aims to COM in providing binary components in multiple languages. The C# override concept uses a corresponding feature of .NET, so .NET is able to effectively utilize inheritance with less vulnerability than COM would have had.

Polymorphism

Virtual functions make it easy to write polymorphic code in C#. Our employee example illustrates the concept of polymorphic code. Imagine a large system with a great many different kinds of employees . How will you write and maintain code that deals with all these different employee types?

A traditional approach is to have a "type field" in an employee structure. Then code that manipulates an employee can key off this type field to determine the correct processing to perform, perhaps using a switch statement. Although straightforward, this approach can be quite tedious and error-prone . Introducing a new kind of employee can require substantial maintenance.

Polymorphism can offer a cleaner solution. You organize the different kinds of employees in a class hierarchy, and you structure your program so that you write general-purpose methods that act upon an object reference whose type is that of the base class. Your code calls virtual methods of the base class. The call will be automatically dispatched to the appropriate class, depending on what kind of employee is actually being referenced.

You trade off some slight degradation in runtime performance for more reliable code development.

The program PolyEmployee\Step1 provides an illustration. The GetPay method is virtual, and methods in the derived class will override it. Here is the code for the base class:

 // Employee.cs  public class Employee  {        public string Name;        public Employee(string name)        {              Name = name;        }  virtual  public decimal GetPay()        {              return 1.0m;        }  } 

Methods in the derived classes override the virtual method in the base class. Here is the code for SalaryEmployee :

 // SalaryEmployee.cs  public class SalaryEmployee : Employee  {     private decimal salary;     public SalaryEmployee(string name, decimal salary)               : base(name)     {          this.salary = salary;     }  override  public decimal GetPay()     {        return salary;     }  } 

The WageEmployee class provides its own override of GetPay , where pay is calculated differently.

 // WageEmployee.cs  using System;  public class WageEmployee : Employee  {     private decimal rate;     private double hours;     public WageEmployee(string name, decimal rate,                         double hours)        : base(name)     {        this.rate = rate;        this.hours = hours;     }  override public decimal GetPay()   {   return rate * Convert.ToDecimal(hours);   }  } 

The payoff comes in the client program, which can now call GetPay polymorphically. Here is the code for the test program:

 // TestPoly.cs  using System;  public class TestPoly  {     private static Employee[] employees;     private const int MAXEMPLOYEE = 10;     private static int nextEmp = 0;     public static void Main(string[] args)     {        employees = new Employee[MAXEMPLOYEE];        AddSalaryEmployee("Amy", 500.00m);        AddWageEmployee("Bob", 15.00m, 40);        AddSalaryEmployee("Charlie", 900.00m);        PayReport();     }     private static void AddSalaryEmployee(        string name, decimal salary)     {        employees[nextEmp++] =           new SalaryEmployee(name, salary);     }     private static void AddWageEmployee(        string name, decimal rate, double hours)     {        employees[nextEmp++] =           new WageEmployee(name, rate, hours);     }     private static void PayReport()     {        for (int i = 0; i < nextEmp; i++)        {           Employee emp = employees[i];           string name = emp.Name.PadRight(10);           string pay = string.Format("{0:C}",  emp.GetPay()  );           string str = name + pay;           Console.WriteLine(str);        }     }  } 

Here is the output:

 Amy       0.00  Bob       0.00  Charlie   0.00 

Abstract Classes

Sometimes it does not make sense to instantiate a base class. Instead, the base class is used to define a standard template to be followed by the various derived classes. Such a base class is said to be abstract, and it cannot be instantiated. In C# you can designate a base class as abstract by using the keyword abstract . The compiler will then flag an error if you try to instantiate the class.

An abstract class may have abstract methods, which are not implemented in the class but only in derived classes. The purpose of an abstract method is to provide a template for polymorphism. The method is called through an object reference to the abstract class, but at runtime the object reference will actually be referring to one of the concrete derived classes. The keyword abstract is also used to declare abstract methods. In place of curly brackets and implementation code, you simply provide a semicolon after the declaration of the abstract method.

An abstract class can be used to provide a cleaner solution of our polymorhphic payroll example. In the Step 1 solution we discussed previously, there was a virtual function GetPay in the base class which returned an arbitrary amount of $1.00. We know that this method is going to be overridden, and in fact the Employee class will itself never be instantiated. Hence we make Employee an abstract class and GetPay an abstract method. This solution is illustrated in PolyEmployee\Step2 .

 // Employee.cs  using System;  abstract  public class Employee  {     public string Name;     public Employee(string name)     {        Name = name;     }  abstract public decimal GetPay();  } 

Sealed Classes

At the opposite end of the spectrum from abstract classes are sealed classes. While you must derive from an abstract class, you cannot derive from a sealed class. A sealed class provides functionality that you can use as is, but you cannot derive from the class and hide or override some of the methods. An example in the .NET Framework class library of a sealed class is System.String .

Marking a class as sealed protects against unwarranted class derivations . It can also make the code a little more efficient, because any virtual functions inherited by the sealed class are automatically treated by the compiler as nonvirtual.

In C# you use the sealed keyword to mark a class as sealed.

Heterogeneous Collections

A class hierarchy can be used to implement heterogeneous collections that can be treated polymorphically. For example, you can create an array whose type is that of a base class. Then you can store within this array object references whose type is the base class, but which actually may refer to instances of various derived classes in the hierarchy. You may then iterate through the array and call a virtual method. The appropriate method will be called for each object in the array.

The program PolyEmployee example illustrates a heterogeneous array of three employees, which are a mixture of salary and wage employees.

for RuBoard


Application Development Using C# and .NET
Application Development Using C# and .NET
ISBN: 013093383X
EAN: 2147483647
Year: 2001
Pages: 158

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net