Using Inheritance in C

Team-Fly    

 
.NET and COM Interoperability Handbook, The
By Alan Gordon
Table of Contents
Chapter Four.  A Quick Introduction to C#

Using Inheritance in C#

With object-oriented design and programming, you design and build your applications by discovering (through analysis and design) and then implementing the key abstractions in the problem domain as well as implementing (or buying) key-implementation abstractions, such as collections, GUI classes, and so forth. One of the key pillars of object orientation is inheritance. Inheritance is how you express an "is-a" relationship between two abstractions (classes). In other words, inheritance is how you model and implement a relationship where two or more types are generalizations /specializations of each other. For instance, if you are building a payroll application, you would likely need an Employee class to model the employees that will be paid through our system. However, most companies have various different kinds of employees. There are the general "rank-and-file" employees who are paid only a salary; managers who receive a salary plus a bonus that is tied to the performance of their department; and executives who receive a salary, a bonus, and stock options. Even though the employee, manager, and executive types have lots of things in common, each type has certain things that are unique to that type. For instance, managers have a bonus amount, and they also have a list of employees that they manage. Executives have all the things that a manager has, but they also have stock options. Although managers and executives have state and operations that are unique to those types, they are also employees and have all of the characteristics of a "rank-and-file" employee, such as a name and an employee ID. That's what I mean by an "is-a" relationship. An executive is a manager, and a manager is a employee.

Inheritance allows us to create types that inherit the interface and implementation of their base type. However, you can also add additional state and operations and even override certain methods from your base types to provide a more appropriate implementation for the specialized version of the class. This last pointthe ability to override certain methods from the base classis key. It means that variables that are typed as a base class may actually reference instances of a subtype of the base class and operations that you call on these base classes may actually invoke different methods depending on what type of object the variable references. This is known as polymorphism, and it is closely related to dynamic binding (also called virtual functions), which I discuss shortly.

Enough with the conceptual discussion. Let's see how to use inheritance and polymorphism in C#. The following code shows a slightly more realistic (but still simple) Employee class.

 public class Employee {   public Employee(int id,     string name,     decimal salary)   {     this.mName=name;     this.mID=id;     this.mSalary=salary;   }   public int ID   {     get { return mID; }     set { mID=value; }   }   public string Name   {     get { return mName; }     set { mName=value; }   }   public decimal Salary   {     get { return mSalary; }     set { mSalary=value; }   }   public decimal GetPay()   {     return mSalary;   }   private int mID;   private string mName;   private decimal mSalary; } 

Notice that this class has three private variables, mID, mName, and mSalary, that contain the state that I am storing for an employee.

 private int mID; private string mName; private decimal mSalary; 

The class also contains a constructor that initializes the state from arguments that you pass in.

 public Employee(int id,           string name,           decimal salary) {     this.mName=name;     this.mID=id;     this.mSalary=salary; } 

In the constructor, you refer to the state variables using the this pointer. C# is just like C++ in that each nonstatic method of a class has an implicit argument called a this pointer, which contains a pointer to the object on which this method is being called. In this case, the use of the keyword this is optional. I could just as easily have defined the constructor as follows :

 public Employee(int id,string name,decimal salary) {   mName=name;   mID=id;   mSalary=salary; } 

You are not limited to one constructor. You can have several constructors with different argument lists. The C# compiler will pick the appropriate constructor based on the argument list that you pass in when you instantiate an Employee instance.

This Employee class also uses properties to expose the ID, name, and salary of an employee as follows for name:

 public string Name {     get { return mName; }     set { mName=value; } } 

The following code shows a Manager class that inherits from Employee :

 public class Manager : Employee {   public Manager(int id,string name,     decimal salary,decimal bonus) : base(id,name,salary)   {     this.mBonus=bonus;     mSubordinates=new Hashtable(50);   }   ~Manager()   {     RemoveAllSubordinates();   }   public void AddSubordinate(Employee emp)   {     mSubordinates.Add(emp.ID,emp);   }   public void RemoveSubordinate(int ID)   {     mSubordinates.Remove(ID);   }   public void RemoveAllSubordinates()   {     mSubordinates.Clear();   }   public Hashtable Subordinates   {     get { return mSubordinates; }   }   public new decimal GetPay()   {     return base.Salary+mBonus;   }   private decimal mBonus;   private Hashtable mSubordinates; } 

The syntax for deriving a subclass from a base class is shown here:

 public class Manager : Employee 

In this case, I am creating a public class called Manager that inherits from a class called Employee . C# only allows single inheritance, so there can only be one base class. The Employee class must either be a class that is defined in the same assembly (as it is in this case), or it must be defined in an assembly that the current assembly references. You can access the public methods, fields, and properties in the base class using the base keyword. I used this during the definition of the constructor as shown here:

 public Manager(int id,string name,decimal salary,decimal bonus) :  base(id,name,salary)  {     this.mBonus=bonus;     mSubordinates=new Hashtable(50); } 

In this case, you pass the ID, name, and salary through to the constructor for the Employee class so that the Employee part of the Managers state can be initialized . You also use the Salary property in the Employee base class to implement the GetPay method as follows:

 public new decimal GetPay() {     return  base.Salary  +mBonus; } 

The GetPay method in the Manager class uses the Salary property from the Employee class to fetch the manager's salary and then adds on her bonus.

Note

Don't worry about the use of the new keyword in the declaration of the GetPay method. I discuss that shortly when I talk about polymorphism.


Only members with a public or protected accessibility level are callable from a derived class. If the Employee class contained the following method:

 public class Employee {     // code elided for clarity ...  private  void GiveRaise(decimal amt)     {      mSalary+=amt;     }     // code elided for clarity... } 

the following code in the Manager class would not compile:

 public class Manager : Employee {     // code omitted for clarity...     public void IncreaseCompensation(decimal salary,         decimal bonus)     {         mBonus+=bonus;  base.GiveRaise(salary);  // compiler error!  }     // code omitted for clarity... } 

You will receive the following error from the C# compiler when you attempt to compile the Manager class:

 Employee.GiveRaise is inaccessible due to its protection level 

If you changed the definition of the GiveRaise method in the Employee class to the following:

 public class Employee {     // code elided for clarity ...  protected  void GiveRaise(decimal amt)     {       mSalary+=amt;     }     // code elided for clarity... } 

The IncreaseCompensation method in the Manager class will compile; this behavior is exactly the same as C++.

Polymorphism

No discussion of inheritance would be complete without a discussion of polymorphism. Consider the following client code that was written using the current definition of the Employee and Manager classes.

 class TestClass {     static void Main(string[] args)     {       Employee emp;       emp=new Manager(2,"Tamber Gordon",800,200);       System.Console.WriteLine("Pay = {0}",       emp.GetPay());     } } 

Notice that I have declared an Employee reference and then assigned it to a new Manager object. The manager has a salary of $800 and a bonus of $200. The question is what will the GetPay method return when it is called through an Employee reference as it is here. The GetPay method will return $800 if it uses the Employee implementation of GetPay, and it will return $1,000 if it uses the Manager implementation of GetPay. With the current definitions of Employee and Manager , the correct answer is $800. To understand why, let's go back to the definition of the GetPay method in the Employee class.

 // The implementation of GetPay in the Employee class public decimal GetPay() {     return mSalary; } 

This method is not declared as a virtual method. With a nonvirtual method, the compiler will bind to the GetPay method at compile time, and it will use the GetPay method associated with the type of the reference (Employee in this case), not the type of the object that the reference points to (a Manager in this case). When a virtual method is called through an object reference, the CLR will use runtime lookup to bind to the method associated with the type of the object that the reference points to. This runtime lookup will start at the type of the object (Manager in this case) and look for a method that matches the requested virtual method. If the CLR does not find a matching method in the Manager class, it will search the base classes in order until it finds a matching method. That way, if the derived class overrides a method, the method in the derived class will be called. If the derived class does not override a method, the version of the method in the base class will be called. This behavior is the very essence of polymorphism.

To make the GetPay method virtual, you simply need to change its definition in the Employee class to look as follows:

 public  virtual  decimal GetPay() {     return mSalary; } 

Now, if you run the following code again, what do you think the result will be?

 class TestClass {     static void Main(string[] args)     {       Employee emp;       emp=new Manager(2,"Tamber Gordon",800,200);       System.Console.WriteLine("Pay = {0}",       emp.GetPay());     } } 

If you said $1,000 (which would indicate that the Manager version of GetPay was called), you are still wrong. The code will return $800, indicating that the Employee version of GetPay was used. So what gives?

OVERRIDING METHODS

One major difference between C++ and C# is that the C# compiler does not automatically assume that you want to override a virtual method in a base class if you declare a method by the same name in a derived class. C++ tended to create fragile base classes because it was very easy to override a method when you didn't intend to. With C#, you must explicitly choose whether you want to hide or override a base class method using the new and override keywords. If you do not use one of these keywords, the compiler will assume that you want to hide the base class method, and it will issue the following warning:

 warning CS0114: [derivedclassname.methodname] hides inherited member [baseclassname.methodname]. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword. 

If you explicitly use the new keyword in the redeclaration of a virtual method in a derived class (as I have done), the method hides the method with the same name in the base class.

 public  new  decimal GetPay() {     return base.Salary+mBonus; } 

Hiding means that, if you call the Manager object through a Manager reference, you will get the Manager's version of GetPay. However, if you call the GetPay method on a Manager object method through an Employee reference, you will get the Employee's version of GetPay.

If you want the version of GetPay in the Manager class to override the GetPay method in the Employee class, you must use the override keyword as follows:

 public  override  decimal GetPay() {     return base.Salary+mBonus; } 

If you use the override keyword in the declaration of the GetPay method in the Manager class, our test code will return $1,000.00, indicating that the GetPay method associated with the Manager class was called.

 class TestClass {     static void Main(string[] args)     {       Employee emp;       emp=new Manager(2,"Tamber Gordon",800,200);       System.Console.WriteLine("Pay = {0}",       emp.GetPay()); // now returns 00.00     } } 

The key advantage of the C# approach is that it provides better support for versioning and makes for less fragile base classes. Let's say I decided to add a Pontificate method to my Manager class (we all know that most managers are prone to such behavior).

 public class Manager : Employee { // I elided the code in the Manager class for clarity   public virtual string Pontificate()   {     return "blah blah blah";   } } 

I can write code that uses the Pontificate method using a Manager reference as shown here:

 Manager mgr; mgr=new Manager(2,"Alan Gordon",800,200); System.Console.WriteLine(mgr.Pontificate()); 

Now let's say that the Employee class came from a third-party library, and, in version 2 of their library, they decide to implement a Pontificate method in the Employee class as shown here:

 public class Employee { // I elided the rest of the code in the Employee class //  for clarity   public virtual string Pontificate()   {     return "When I started at this company..."";   } } 

It was probably not your intention to override the Pontificate method in the Employee class when you created a Pontificate method in the Manager class. Indeed, it couldn't have been your intention because the Employee class did not have a Pontificate method when you created the Manager class. The decision to override a method in a base class should always be a carefully considered one. You should always consider whether you need to call the base classes' implementation of the method. Failure to do so could cause the base class to break. With C#, the Pontificate method in the Manager class cannot accidentally override the Pontificate method in the Employee class.

ABSTRACT CLASSES

Like C++, C# supports abstract classes. An abstract class is a class where one or more of the methods in the class is left unspecified. Abstract classes make sense when you want to define a signature for a method in a class so that all classes that inherit from the class must support the method. However, the base class in which you want to define the method is so generic that you cannot provide a suitable implementation of the method in that class. You cannot create an instance of an abstract class. To create an instantiatable class, you must derive a class from the abstract class and provide an implementation for all of the abstract methods in the class. This derived class is now called a concrete class. Even though you cannot create an instance of an abstract class, you can declare a reference that is typed to the abstract class and then point that reference to an object that is typed as a concrete class that derives from the abstract class.

I can probably make this clearer using an example from the Employee class. With its current design, this class will only support salaried employees. Ideally, you would like to support hourly employees as well as contractors, and consultants who may be paid hourly or a lump sum. The best way to support a wide variety of compensation algorithms is to redesign the Employee class and make it more generic. I will remove the Salary property and the mSalary member from the Employee class. I will then create SalariedEmployee and HourlyEmployee classes that derived from the Employee class. I will leave the declaration of GetPay method in the Employee class. However, given the wide variety of compensation schemes that may be applied to an Employee, it is impossible to provide an implementation for the GetPay method in the Employee class, so we will turn it into an abstract class as follows:

 public abstract class  Employee {   public Employee(int id,string name)   {     mName=name;     mID=id;   }   ~Employee()   {     MessageBox.Show("Destructor called");   }   public int ID   {     get { return mID; }     set { mID=value; }   }   public string Name   {     get { return mName; }     set { mName=value; }   }   public abstract decimal GetPay();   private int mID;   private string mName;   } 

Notice that I used the abstract keyword both on the GetPay method and the class itself. Also notice that I have not provided an implementation of the GetPay method. The following code shows how you would create a SalariedEmployee class that inherits from the Employee class and provides an implementation for the GetPay method.

 public class SalariedEmployee : Employee {   public SalariedEmployee(int id,string name,     decimal salary) :       base(id,name)   {       mSalary=salary;   }   public decimal Salary   {       get { return mSalary; }       set { mSalary=value; }   }   public override decimal GetPay()   {       return mSalary;   }   private decimal mSalary; } 

The SalariedEmployee class is now a concrete class. The following code will succeed:

 SalariedEmployee salEmp=new SalariedEmployee(5,"Alan Gordon",500); 

As will this:

 Employee emp=new SalariedEmployee(5,"Alan Gordon",500); 

However, the following code will not compile because you are trying to create an instance of an abstract class.

 Employee emp=new Employee(5,"Alan Gordon"); 
INTERFACES

Somewhat related to abstract classes are interfaces. An interface is a set of related methods that together define some behavior. An interface has no associated implementation for any of its methods; it just defines the prototype for the methods. You can almost think of an interface as an abstract class where all of the methods in the class are left abstract. In fact, people usually implement interfaces in C++ as abstract classes that contain only abstract (unimplemented) methods. To use an interface, you simply add a comma-delimited list of the interfaces that your class will implement using the same syntax that you use for inheritance as follows:

 public class SalariedEmployee : Employee,  IDisposable, IMyInteface  { //... the rest of the class definition is omitted for clarity } 

Notice that the Employee base class is listed first. If your class inherits from a base class and implements one or more interfaces, the base class must appear first in the comma-delimited list of base classes and interfaces. One key difference, though, between inheritance and implementing interfaces is that a class can implement multiple interfaces, but it can only inherit from one base class.

If you wanted to turn the Employee abstract class into an interface, you would write the following code.

 public interface IEmployee {     void Work(int numHours);     decimal GetPay();     void Terminate(); } 

An acid test of whether inheritance makes sense or not is that the sentence "a [ derivedclassname ] is a [ baseclassname ]" should make intuitive sense. For example, "a manager is a employee." Interfaces, however, are used to model a "has the behavior of" or a "behaves like" relationship. In other words, the sentence "a [ implementingclassname ] behaves like a [interfacename]" should make intuitive sense. In many cases, both sentences will make sense.

In an object hierarchy like the one that I have created, interfaces are useful when you find yourself needing to implement the same behavior in a number of classes. It's a good tipoff that you need to create an interface when you find yourself implementing the same set of methods over and over again in many classes. By factoring these methods into an interface and then implementing those interfaces in your classes, you gain a commonality of expression that you can exploit. You can create reference variables typed to that interface and then point those variables at an instance of any class that implements the interface.

Once again, let's create an example using the Employee classes. After I split the hierarchy into salaried and hourly employees, I have a problem. Which of these classes should the Manager class derive from? I could have managers who were either salaried or paid hourly. This means that we will need 2 Manager classes, SalariedManager and HourlyManager, but both of those classes will share similar functionality. For example, a manager has subordinates and does performance reviews. Additionally, I would like to be able to write code that operates on all managers whether they are salaried or paid hourly. You can accomplish this using an IManager interface as follows:

 public interface IManager {     void AddSubordinate(Employee emp);     void RemoveSubordinate(int ID);     void RemoveAllSubordinates();     Hashtable Subordinates();     decimal GetBonus(); } 

You can then create a SalariedManager class that implements the IManager interface as follows:

 public class SalariedManager : SalariedEmployee,  IManager  {   public SalariedManager(int id,string name,     decimal salary,decimal bonus) :     base(id,name,salary)   {     this.mBonus=bonus;     mSubordinates=new Hashtable(50);   }   ~SalariedManager()   {     RemoveAllSubordinates();   }   public decimal GetBonus()   {     return mBonus;   }   public void AddSubordinate(Employee emp)   {     mSubordinates.Add(emp.ID,emp);   }   public void RemoveSubordinate(int ID)   {     mSubordinates.Remove(ID);   }   public void RemoveAllSubordinates()   {     mSubordinates.Clear();   }   public Hashtable GetSubordinates()   {     return mSubordinates;   }   public override decimal GetPay()   {     return base.Salary+mBonus;   }   private decimal mBonus;   private Hashtable mSubordinates; } 

The following code shows how you would use the SalariedManager class and the IManager interface:

 1.  static void Main(string[] args) 2.  { 3.  Decimal decBonus; 4.  Employee empMgr=new 5.      SalariedManager(1,"Tamber Gordon",1800,200); 6.  Employee empEmp=new 7.      SalariedEmployee(2,"Alan Gordon",500); 8.  IManager mgr = empMgr as IManager;  9.  if (mgr != null) 10. { 11.   decBonus=mgr.GetBonus(); 12.   mgr.AddSubordinate(empEmp); 13. } 14. else 15.    Console.WriteLine("Not a manager!"); 16. } 

The key line of code here is line 8 where you cast the empMgr Employee reference (which actually points to a SalariedManager object) to a IManager interface reference using the as operator. If the Employee reference can be cast to an IManager interface reference, you then call the GetBonus and AddSubordinate methods on the IManager interface on lines 11 and 12.


Team-Fly    
Top
 


. Net and COM Interoperability Handbook
The .NET and COM Interoperability Handbook (Integrated .Net)
ISBN: 013046130X
EAN: 2147483647
Year: 2002
Pages: 119
Authors: Alan Gordon

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