Inheritance


To explain our usage of inheritance and polymorphism, we will use UML (Universal Modeling Language) diagrams to explain class relationships.

A good general reference is Instant UML by Apress, ISBN 1-86100-087-1. For details on using UML with .NET, there is also the more recent Professional UML with Visual Studio .NET, ISBN 1-86100-795-7.

UML can be used to describe entire applications showing relationships between classes. There are two main kinds of relationship that we'll concern ourselves with here – either the is a or has a relationship concept of OO development. This is always the rule of thumb as to whether a class should derive from another class or should contain another class (this is the background of the is keyword used in C#, which we shall discuss later in the chapter).

These relationships can be best understood by describing them in terms of the Mammal class hierarchy we discussed earlier. We can say that a Monkey is a Mammal since it can do everything that a Mammal can do (and more). We also said earlier that a Monkey has aTree. A Monkey can't do things a Tree can do, but we can access the functionality of a Tree by obtaining one from a Monkey.

The is a relationship is fundamental to polymorphism. If a Monkey is a Mammal, then any code we write that applies to Mammals applies equally to Monkeys. So, we can substitute a Mammal with a Monkey, and polymorphism will apply. As we said before, polymorphism is provided through inheritance in .NET; it follows, therefore, that an is a relationship in .NET can generally be modeled with inheritance, and we should make our Monkey class inherit from Mammal.

The has a relationship has an impact on inheritance too. If a Monkey has a Tree, then we should expect any classes derived from Monkey to have a Tree also. Similarly, if we say that a Mammal has a LatinName, then it follows, because a Monkey is a Mammal, that a Monkey has a LatinName.

The following UML diagram represents the basic relationship between the Mammal and all of the classes that derive from the Mammal class. The arrow notation is all that we need to show the relationship of inheritance. In this example, the Mammal class is the base class and the derived classes are the Human, Monkey, and Mouse:

click to expand
Figure 1

Inheritance Hierarchies

As there are different types of monkeys and humans, which may have different attributes from one another, we can extend the inheritance hierarchy by making the derived classes base classes to other classes. We could therefore extend this inheritance hierarchy to establish two distinct types of Monkey – either an Ape or a Chimpanzee. An ape or chimpanzee should have the characteristics of a monkey, such as a Climb() method, but not those of humans. We provide different operations and attributes for the two new types of monkey; for example, the ape being territorial, may have a Fight() method that a chimpanzee may lack, and the chimpanzee may have a Flee() method instead.

A revised model showing this new derived class relationship may be illustrated by the following figure, showing a second derived level.

click to expand
Figure 2

The Ape is a Mammal but it is a Monkey also, which has a different implementation from a Mammal. Note that this model still lacks the description necessary to tell us what each class can do over and above the base class. We can use the class symbol in UML to add more information such as operations and attributes about the class. The top section of the shape refers to the class name. The second section contains the names of class attributes. The final section contains operations.

click to expand
Figure 3

In the case of the Mammal class, the # symbol preceding the members means that both Sleep() and Eat() are protected members of the class, which means that they are accessible only to code contained in the class itself, and derived classes. The symbol indicates a private member and the + symbol indicates a public member. In this diagram, we've added members to the Human class: a Work() operation, since humans are the only mammal which works, and an attribute to encompass the higher human emotions, which is a Boolean value to indicate whether the human is happy or sad. Similarly, the Monkey has a Climb() method as monkeys climb trees, while not all mammals normally climb trees, so we cannot implement it on the Mammal base class. There is no extra implementation of operations or attributes for a mouse since the mouse is a model mammal and will need very little more than sleeping or eating, although we could provide an extension for this class too, maybe in the form of a RunRoundWheel() method.

There is no need to redefine the extra operations or attributes pertaining to the base class since this is implicit in the UML model, as we have defined an inheritance association between the classes. By looking at this diagram we can assume that the human is capable of doing three operations:

  • Sleep()

  • Eat()

  • Work()

and it has a single attribute:

  • Happy

Overriding Base Class Operations

The base class exposes a series of operations (methods and properties) to the derived class, which can be overridden. We can allow derived classes to override the base class operations by making them virtual; this means that all these operations can have a new implementation in the derived class. Although methods marked as non-virtual cannot be overridden to provide a new implementation to the base class, we can define an operation with the same signature that will hide the respective base class operation. In the Mammal class hierarchy, the Sleep() operation should be virtual since each class will have a different implementation – for example, the mouse may need more sleep than the human or the monkey, and so on. So we can ascertain different methods for each of the derived classes based on how they differ from each other and whether they can inherit the base class implementation without changes. Defining an operation as virtual in the base class is represented in the UML class diagram by simply adding a method of the same name as the base class method to the derived class diagram.

If an operation is marked as virtual in the base class, it means that the derived class has the option to override the base class implementation. If it chooses to implement the operation, then it must use exactly the same signature as in the base class. We'll see how this is done using C# later in the chapter. However, each method signature that is overridden in the derived class must be marked to acknowledge that a new implementation is provided in the derived class.

Abstract Classes and Interfaces

An important concept in inheritance is the idea of the concrete class. The only classes we can instantiate are concrete classes. In order for an instance of a class to exist, it must have a fully defined implementation for every method in its interface. A class that meets these criteria is called a concrete class. Now, if all we could define were concrete classes, this distinction wouldn't be necessary, but there are two mechanisms we can use in .NET to define classes without defining implementations – interfaces and abstract classes.

An abstract class is a class that contains at least one abstract method. An abstract method is one that declares a signature, but no implementation. The implementation must be provided by creating a class derived from the abstract class, and adding an implementation there. If a class derived from an abstract class does not define an implementation for an abstract method, then the derived class is also abstract. An abstract class is the opposite of a concrete class. It cannot be instantiated, and it is used when generating class inheritance hierarchies to provide a more realistic inheritance model.

An interface is like a pure form of abstract class. While an abstract class may contain definitions for some methods, an interface contains only signature definitions. Because an interface cannot define implementations, they can be treated specially by the inheritance system, and we'll see how later.

Abstract classes are used as base classes providing implementation code where necessary, which will be inherited by all the classes. We could declare Mammal as an abstract class; it makes no sense to ever want to create an object of type Mammal that is not also of one of the subtypes of Mammal. It is considered good design to make an abstract class the base class in an inheritance model.

Each abstract operation that is declared in an abstract class is implicitly declared as virtual, because you have to either override this method, or declare it abstract in a derived class. This is not mandatory for non-abstract methods as the derived class can use the implementation of the abstract base class methods if necessary. To override the non-abstract methods declare them as virtual in the base class.

Both abstract classes and abstract methods are denoted in UML through the use of italics. Although the Mammal model contains a single method it is possible to have multiple abstract and non-abstract methods denoted by combinations of italic and non-italic text.

click to expand
Figure 4

Types of Inheritance

There are two distinct approaches to inheritance – the first approach is implementation inheritance whereby the base class contains an implementation. This implementation can be overridden if operations are declared as virtual or must be overridden if operations are declared as abstract. This is the kind of inheritance we have considered so far. The second type is interface inheritance. Implementation inheritance requires interface inheritance; if we inherit the implementation of a method, we also inherit the method's signature, through which the implementation may be called. But as we say with abstract methods, we can inherit the obligation to provide an implementation, without inheriting an implementation. This is inheritance of interface. Using a pure interface type, you can declare a set of methods that a class must implement in order to inherit the interface. Every derived class must implement the operations specified on the interface.

In the diagram below the Mouse class implements the ISmallAnimal interface

click to expand
Figure 5

UML notation for interfaces is slightly different from that of classes. In the example above the use of <<interface>> specifies that ISmallAnimal is an interface not a class. An interface cannot contain any implementation, only operation signatures. For this reason interfaces will not have attributes declared. We can use dotted lines to encompass relationships between interfaces and other interfaces or classes. As you can see, the Mouse class provides an implementation of the Size() operation.

An interface provides a means of specifying the contracts of methods, but not their implementation. We use interface inheritance to provide different implementations of interface methods in a class. The collection classes in the Framework Class Library use interface inheritance to define their implementation for a variety of methods, such as Sort(), Reverse(), and Add(). An example using an ArrayList can be seen below, where an ArrayList is created, which implements ICollection, and hence is of type ICollection. We could use a Hashtable class instead of an ArrayList and our method won't differentiate between them because they both implement ICollection:

    public static int ReturnCount(ICollection col)    {      return col.Count;    }    public static void Main()    {      ArrayList ar = new ArrayList();      int count = ReturnCount(ar);    } 

Multiple Inheritance

The CLS employed by the Microsoft CLR implementation requires that a class is only able to inherit from a single base class, and so cannot have two direct base classes. By avoiding the complexity inherent in multiple inheritance, .NET saves us a lot of headaches, and in those circumstances where we want to combine the capabilities of two classes into one, there are other mechanisms available. When we want to indicate that an instance of a class is more than one thing (has multiple is a relationships), we can do so with multiple interface inheritance, which we'll look at in a moment. The approach to multiple inheritance is very similar to the approach taken by the Java language designers but is different from that of C++. C++ does support multiple implementation inheritance but C# doesn't. Although it seems that multiple implementation inheritance can only make writing code easier consider Figure 6:

click to expand
Figure 6

The class Human inherits from both Mammal and Species. Mammal itself is derived from Species. When all the links are resolved Human will effectively inherit the methods of Species twice. But on one side, what's to stop the Mammal class from overriding some of the methods in Species? In that case, how are we to resolve calls to methods on the Human class? It's actually much easier for us that this has been avoided in the CLR.

The same problems do not occur when the inheritance is only of interface, not implementation. As we said, inheriting an interface is inheriting an obligation to provide an implementation. You can inherit that obligation several times, and there will be no problem. You can inherit the same obligation from two different interfaces, and fulfilling it fulfils both interfaces. So, while multiple inheritance of implementation creates headaches, multiple inheritance of interface can help us solve many problems.

The following example of the use of the two techniques in conjunction within the .NET Framework Class libraries concerns the collection classes. For example, an ArrayList class implements the IList, ICollection, IEnumerable, and IClonable interfaces and therefore provides its own implementation for enumeration, cloning, and standard collection class methods such as Add(). The DomainUpDownItemsCollection that is used by the DomainUpDown Windows Forms Control derives from the ArrayList emphasizing the use of the implementation and interface inheritance in the same hierarchy. We can define the interfaces and a base class at the same level of the hierarchy, although the implementation below shows them at different levels.

click to expand
Figure 7

Creating a Class Hierarchy

In this section we'll look at some small examples to create a new class hierarchy, which encompasses many concepts that we have introduced in the UML section. This will include illustrating inheritance and polymorphism principles in code, creating abstract and concrete classes, and also creating implementation classes for multiple interface inheritance.

To illustrate, we can create two class types and one struct type. The Example1 class will not explicitly inherit from anything, but the Example2 class will inherit from the System.Object class. The Example3 struct will also not explicitly inherit from anything.

    public class Example1    {//some code    }    public class Example2 : System.Object    {//some code    }    public struct Example3    {// some code    } 

To find out the class inheritance structure, we can add code to the Main() method to return the type of the class and its base class; this will use reflection to get the type instance. Remember that each class implicitly inherits from System.Object, yet in Example2 we have explicitly inherited from System.Object.

It is important to understand that whether or not an object inherits from System.Object, the compiler will add this for us since it is implicit in each class definition in all .NET languages. The console will display the name of each class and also display that both Example1 and Example2 inherit from System.Object, as it is the ultimate base class of all .NET classes. The Example3 struct will inherit from System.ValueType – which will be displayed on the Console as the base class. The important point here, though, is that System.ValueType also inherits from System.Object and is simply used to provide an overridden implementation of the System.Object methods that is more relevant to a value type.

    public class MainMethod    {      static void Main()      {        Example1 example1 = new Example1();        Console.WriteLine("Class type instance is {0}                   and derives from {1}", example1.GetType().Name,                   example1.GetType().BaseType);        Example2 example2 = new Example2();        Console.WriteLine("Class type instance is {0}                   and derives from {1}", example2.GetType().Name,                  example2.GetType().BaseType);        Example3 example3 = new Example3();        Console.WriteLine("Class type instance is {0}                    and derives from {1}", example3.GetType().Name,                   example3.GetType().BaseType);      }    } 

The above code, called example_inheritance.cs, when executed will produce the following output:

    C:\Class Design\Ch 07> example_inheritance    Class type instance is Example1 and derives from System.Object    Class type instance is Example2 and derives from System.Object    Class type instance is Example3 and derives from System.ValueType 

The compiler will prevent you from creating your own value types by inheriting from System.ValueType. The CLR doesn't distinguish between classes and structs (that is, reference types and value types) directly. It uses the inheritance path to distinguish between the two enabling optimization of value types. The CLR can optimize how it handles value types since they are effectively sealed. Remember that a sealed class cannot be inherited from or derived from.

Class Definitions

Examining the MSIL output through the .NET Framework tool ILDASM allows us to check this correlation ensuring that the three types inherit from System.Object or System.ValueType respectively. An important point to draw attention to is that the struct value type Example3 has been marked sealed in the IL definition, meaning that we cannot use this type as a base class for anything. By default all value types contain this definition implying that we can't use any form of polymorphism with value types, which is all the CLR needs to know to optimize the behavior of memory allocation for a struct. An object instance will require a table of methods and the use of indirection to be able to support implementation inheritance and interface inheritance; effectively there is no need to engage in a lookup to find out what method to use, that is, the base class or overridden method, as the runtime knows that this can only have the one specified value, and this overhead can be avoided with a value type since it is effectively sealed.

click to expand

click to expand

Designing a Base Class

We saw how to construct a class hierarchy with the Mammal as a base class and then derive classes, which inherit the methods of the mammal but provide definitions for new methods specific to the class. It is important to plan a class hierarchy from the outset, since any changes at source to the base class to the visible interface will result in changes to all of the classes that inherit from the base class.

The Teacher class hierarchy below defines two field values, Salary and Grade, and a single method CalculateDaysWorked(). The derived classes HeadTeacher, SupplyTeacher, and StaffTeacher are respectively the head school teacher, a supply teacher with a temporary post, and a staff teacher. We have the head teacher who will never teach, and a staff teacher and a supply teacher who will always teach.

click to expand
Figure 8

For example, the method CalculateDaysWorked() in the base-class Teacher, which will return the number of days that each teacher has worked so far in the year, has different implementations in each of the three concrete classes that derive from Teacher.

Suppose we want to accept another parameter and index to look up this value. Changing this method signature in the base class will affect all of the derived classes as they will still have to provide an implementation, which has to match the base class implementation. Having many derived classes means there will be a big rewrite. We can prevent this by using the internal modifier, which ensures that only classes within the same assembly will be able to see the classes that they wish to derive from. This is a way of ensuring that classes within our own assemblies can be inherited from but ensuring that other software developers cannot subclass our own classes from classes outside its assembly, ensuring that visible changes to the new base class will not break a software application.

Abstract vs. Concrete

Declare the base class Teacher as abstract, and declare all fields as protected, not private as they wouldn't be visible to the derived class. The derived class could provide a level of encapsulation by creating property accessors for the fields, or declare property accessors as protected in the base class and the underlying fields as private. In this way if the base class implementation changes the derived class will not be affected. By default, the methods are not considered abstract. The IsWorking() method can be overridden in derived classes as it is declared virtual; it will allow the value of the private isWorking variable to be changed.

    using System;    public abstract class Teacher    {       protected int salary;       protected int grade;       private bool isWorking = false;       public abstract int CalculateDaysWorked ();       public virtual void Work(bool isWorking)       {          //we could add some logging code here          this.isWorking = isWorking;       }       protected bool IsWorking       {          get { return isWorking; }       } 

We could similarly define a constructor for this class but it would be better to mark it protected so that only our derived classes can access it. An alternative implementation of this class is exposing two property accessors, which write to private fields Salary and Grade in the base class. This will loosen the coupling between the base class and the derived class, since all the derived classes don't need to directly depend on the field types. Providing protected property accessors means that we can change the implementation in the base class to reflect a change in the field declaration. This means that the property accessor can always have the same method signature but the implementation code can change without any overhead to the derived class.

We could simply present a property accessor to the derived class that would get or set the value of the private int field. The property accessor would take a long argument and convert it into an int value (and vice versa).

    protected Teacher(int Grade)    {       this.grade = Grade;    }    protected long Grade    {       set       {this.grade = (int)value;       }       get       {return (long)grade;       }    } 

Using Virtual Methods

We can add another generic virtual method that can be overridden only if necessary. We can also add a CalculatePay() method, which would calculate the monthly pay of a teacher based on their annual salary, including overtime. The salary field would contain the annual salary; if this is 0 the method will throw an exception; otherwise it will return the monthly salary plus the overtime. The overtime would be stored in the overtime field.

      protected int overtime;      public virtual double CalculatePay()      {        if(salary==0)        {         // throw new Exception("Unknown salary value");        }        return (salary / 12) + (overtime * (salary / (52 * 40)) * 1.5);      }    }    public class AbstractTeacherExample    {       public static void Main(string[] args)       {       }    } 

Occasionally you would need to override this method in derived classes, where we can have another implementation, returning a different value, for the SupplyTeacher class as it is based on a daily rate depending on the grade; a staff teacher's salary calculation is the same as the virtual method, and the head teacher has a similar calculation but with double time overtime and a yearly supplement based on their grade.

We have saved the file as abstract_teacher.cs. The MSIL shows the definition for the Teacher class and the CalculateDaysWorked() method. In the MSIL, the class is declared abstract, and the CalculateDaysWorked() method is set as virtual. We don't declare it virtual as all abstract methods are virtual by default.

click to expand

We have two new keywords – hidebysig and newslot. In conjunction with the virtual keyword hidebysig denotes that this method should be hidden in a derived class, if a method with the same signature exists, to avoid ambiguity when invoking methods.

The newslot keyword creates a new slot within a vtable (a virtual function table), which is a table of methods that each .NET object contains comprising all inherited methods and overridden as well as static methods. The vtable is the mechanism by which the CLR decides whether to invoke the virtual or overridden method on the derived class.

Sealing Classes

Another important concept that is enforced throughout the .NET Framework is the use of sealing classes or methods, to avoid classes from being inherited, or methods being overridden. While abstract classes ensure the class acts as a base class, sealed classes prohibit the class from being the base class. To make only one type of head teacher, which is defined by the HeadTeacher class you will create a definition for the HeadTeacher sealing the class to prevent it from being a base class to other classes. If you compiled the HeadTeacher class code now with the following sealed class definition, and chose to derive another class from it the C# compiler would generate an exception.

    public sealed class HeadTeacher 

Similarly, declaring a sealed method will prevent you from overriding it in a derived class. MSDN documentation specifies that sealed classes can also be used for classes that are not inherited. A sealed method cannot be first declared in a base class, it has to be overridden in a derived class and declared as sealed. Any class that then derives from this derived class will not be able to override the sealed method. The following code is saved as sealing_classes.cs:

    public class SupplyTeacher : Teacher    {      private int monthDays;      public override sealed double CalculatePay()      {        if(monthDays==0)           monthDays = GetMonthDays(DateTime.Now.Month,                                    DateTime.Now.Year);        return (100 + (this.Grade * 10)) * monthDays;      }      protected int MonthDays      {        set { monthDays = value; }      }      private int GetMonthDays(int month, int year)      {      if(month==9||month==4||month==6||month==11)        {          return 30;        }        if(month==2&&(year%4==0))        {          return 29;        }        else if(month==2&&(year%4!=0))        {          return 28;        }        return 31;      }    } 

This section has reviewed some of the responsibilities of the base class and how it is used to affect behavior in any derived classes.

In the first two lines shown below, note that abstract, as an IL instruction is the converse of final (and by implication in C# also the converse of sealed) as the former ensures that the method must be overridden in the derived class, and the latter prevents the method from being overridden in the derived class.

click to expand

Deriving Classes

Returning to the Teacher model, we'll describe the three derived classes that we built into the model. When we create an instance of a derived class, the base class constructor will be invoked. The interesting point here is the order of precedence, since the base-class constructor will be invoked before that of the derived class. This top-down construction means that the System.Object constructor will be invoked first followed by all the derived class until the constructor of the ultimate derived class is invoked.

The SupplyTeacher class passes the int parameter into its constructor and then invokes the base-class constructor (Teacher) of the same signature. This will populate the grade field with a value other than the initial value of 0. We can then calculate a value for the salary field. The overridden method CalculatePay() is invoked to populate the salary field and a yearly salary is calculated. Note that if we place the CalculatePay() method invocation within the Teacher base class code block, even though the Teacher class has its own method CalculatePay(), the SupplyTeacher method CalculatePay() will still be invoked. This is because we are creating a derived-class type not a base-class type, and therefore the context of this object instantiation is known by the runtime along with the fact that the method has been overridden in the vtable.

    public SupplyTeacher(int number): base(number)    {      salary = CalculatePay() * 12;    }    public abstract class Teacher    {      protected Teacher(int Grade)      {        this.grade = Grade;      }      //all the other methods, properties and fields here    } 

The use of initialization code is that, even though the derived class hasn't initialized, it still has access to the methods and properties of the base class. If we revert to the SupplyTeacher class, we can see that the syntax for inheritance is the colon (:) that specifies that SupplyTeacher inherits from Teacher. This syntax should be familiar to C++ developers; however, Java developers can associate this with the extends keyword, and the use of super instead of base to reference the base class. The code snippet shown below is a part of deriving_classes.cs:

    public class SupplyTeacher : Teacher    {      public SupplyTeacher(int number) : base(number)      {        Console.WriteLine(base.Salary);        salary = CalculatePay() * 12;      }      //other methods and properties and fields here    } 

Overriding and Shadowing Methods

The signature is as important as the name while specifying methods in the derived class. Let's create another method called CalculatePay() in the derived class StaffTeacher that will be used to add a monthly supplement to the monthly salary of the teacher; recall that the CalculatePay() method contained no arguments.

The standard model of defining virtual methods on the base class means creating an overloaded method for CalculatePay(), and expecting the derived class StaffTeacher to override it. However, this method overload will be visible to all other classes irrespective of whether they are overridden on any of the derived classes. Ideally, we don't want any derived classes except StaffTeacher to implement this overloaded method. This is impossible to implement by using a second overloaded virtual method – instead we create another method with the same name and return type but a different argument signature in the derived class StaffTeacher – this will appear as a single method with two overloads even though one is an overridden method and the other contains a completely new signature undefined in the base class.

We can use this with all the other derived classes to create overloaded methods with different signatures and hence have their own overloaded versions of these methods. This technique is called shadowing. In the example opposite, called overriding_and_shadowing.cs, we are allowed to declare all the extra overloaded methods with or without the new keyword, as the method doesn't hide an equivalent signature method in the base class.

    public class StaffTeacher : Teacher    {      public StaffTeacher(int code) : base(code)      { }      public override double CalculatePay()      {        return base.CalculatePay();      }      public double CalculatePay(int monthlySupplement)      {        if(monthlySupplement > 1000) throw new Exception("Supplement out                                                        of range exception");        return base.CalculatePay() + monthlySupplement;      }    } 

If the CalculatePay() method is omitted, all method requests to the derived class matching this signature will be served by the virtual base class method of the same signature. The method itself can have its own implementation, which may include a call to the base class method, which will invoke the virtual base-class implementation of the method.

Virtual Method Dispatching

Virtual method dispatching embodies the idea of polymorphism. If you have a base-class reference to the derived class type instance, you can call any of the overridden methods of the derived class using the base-class reference. This enables you to substitute a base-class reference in code for a derived-class reference and remain ignorant of the derived type.

Even if you returned a reference to the Teacher class by creating a new instance of SupplyTeacher class, the code would see the interface (all of the public methods, properties and fields) of the Teacher class. This uses polymorphism to achieve implementation of code.

The following mechanism is known as virtual method dispatching, which helps you determine the type of class the variable is an instance of, by using the is or the as operator to check for a particular type.

    Teacher st = new SupplyTeacher();    Console.WriteLine(st.CalculatePay());    //this will invoke the method on the derived class SupplyTeacher 

Non-Virtual Method Dispatching

We can use non-virtual method dispatching to avoid the polymorphism that was used earlier. If you create a derived class instance exactly like the virtual method dispatching example above, you will not invoke derived class methods that don't override base class methods. Instead, you will invoke the base class method.

The compiler generates a warning message when we try to compile this code without the override keyword. The warning message says that the derived method has the same signature as the base class method and is therefore hiding it. We can get rid of this warning message by redefining the method in the derived class adding the new keyword. In the Teacher class we can declare the GetTaughtSubjectName() method.

    protected string subject;    public virtual string GetTaughtSubjectName()    {      return subject;    } 

In the StaffTeacher class we can declare the equivalent method with the same public signature.

    public string GetTaughtSubjectName()    {      return this.subject;    } 

Using the following code (nonvirtual_teacher.cs) to return the base class reference for a derived class instance and invoking a method using the reference will result in the base class method being invoked.

    Teacher st = new SupplyTeacher();    Console.WriteLine(st.GetTaughtSubjectName());    //this will invoke the method on the base class Teacher 

Polymorphism in Action

Another pronounced implementation of polymorphism is using the Teacher reference as an argument to a method, so that you don't have to know which derived class is being used, and you can treat it as a base class via a principle known as substitutability. In the method InvokeMemberOnTeacher() you pass in an object reference of type Teacher, which can refer to any kind of object whose type inherits from Teacher. As an instance of the derived class, SupplyTeacher is really being passed in the overridden method CalculatePay(). You return a string argument that contains the name of the type being passed in, so that the actual class instance is the derived type, which will be returned from this GetType invocation. Avoid using the is and as operators with polymorphism, as this defeats the principle of polymorphism, and with every derived class you add to the class hierarchy, you will have to update this method accordingly.

    public class InvocationClass    {    public static int InvokeMemberOnTeacher(Teacher teach, out string    WhichTeacher)    {          int pay = teach.CalculatePay();          WhichTeacher = teach.GetType().Name;          return pay;    }    static void Main()    {          Teacher st = new SupplyTeacher(6);          string teacherString = String.Empty;          Console.WriteLine("Teacher Pay: {0}",                             InvocationClass.InvokeMemberOnTeacher                                                  (st, out teacherString));            }    } 

The Main() method creates a SupplyTeacher class instance using a Teacher base-class reference. It will invoke the static method InvokeMemberOnTeacher() and display the output on the Console window. The output from the invocation is shown below.

    C:\Class Design\Ch 07>polymorphism    Teacher Pay: 4960 

MSIL code generated for the output of the CalculatePay() invocation contains an instruction callvirt, which is used only when calling virtual methods in place of the normally used call instruction. In contrast to callvirt, call simply uses the vtable of the current type reference, and can be used in our non-virtual method dispatching examples earlier. The callvirt gets information about the concrete type, and determines whether to invoke a virtual method on the type's base class, or the overridden method on the type. Note that the Teacher class hierarchy is in the Inheritance namespace.

The vtable simply contains a list of memory pointers to various methods owned by the class instance. It distinguishes between virtual and non-virtual methods by providing different areas for each. Method tables of the derived class and the base class will be similar in their structure, and the methods of the virtual method declarations will correspond to both of them. Both use a vtable that contains a series of pointers pointing to the positions in memory of each of the methods. Each virtual method adds the MSIL instruction newslot, which means adding another slot to the vtable. This addition only occurs in the virtual method section.

The newslot instruction adds a new entry at the end of the vtable, offset from where the last method pointer is encountered. Overriding the base class virtual method in the derived class will not add a newslot instruction, which prompts the CLR to check whether the method signature is the same in the base class and the derived class. If not, then it will be added to the end of the vtable, as the omission of the newslot instruction is just used to prompt the CLR to look at the base class's vtable.

If it is the same, it will replace the inherited vtable entry for that method, which will add the method pointer for the derived class's overridden method. The following MSIL code shows the StaffTeacher method CalculatePay() that omits the newslot instruction and is therefore treated as if it is supposed to replace the virtual method in the base class.

click to expand

The figure below shows a possible vtable layout for the StaffTeacher class instance. It contains all the virtual methods in the top half of the object, and the non-virtual methods in the lower half followed by all the instance and static methods.


Figure 9

The following diagram shows a fuller description of the above diagram by including support for distinction between inherited virtuals and additional virtuals. This is necessary since the overridden methods will replace the equivalent method in the inherited virtual method.

click to expand
Figure 10

The garbage collector (GC) data section contains information that will be used for cleanup including whether an object has been marked for deletion, generation information, and so on. The Header contains a pointer to a memory interface map, which contains a set of pointers from supporting interfaces, back to the method table where the methods have been implemented. This enables any supported interfaces to find the methods being implemented in the implementation.

Finally, if we look at the class declaration in MSIL for the StaffTeacher class, we can see that the extends keyword is used to denote inheritance (syntactically similar to Java). If the class is not derived from another class then the default is for the MSIL to extend System.Object. So even when the class denotes that the Teacher class is the base class, the ultimate base class is always System.Object.

click to expand




C# Class Design Handbook(c) Coding Effective Classes
C# Class Design Handbook: Coding Effective Classes
ISBN: 1590592573
EAN: 2147483647
Year: N/A
Pages: 90

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