Chapter 7
Specialization, Extension, and Inheritance

Kurt Nørmark ©
Department of Computer Science, Aalborg University, Denmark


Abstract
Previous lecture Next lecture
Index References Contents

In this chapter of the material we study inheritance.

At the conceptual level, we start by a study of class specialization and class extension. Following that the programming language concept of inheritance is introduced.

In the last part of the chapter we study inheritance in C#. This includes polymorphism, dynamic binding, abstract classes, and a number of design patterns for which inheritance is of central importance.


Specialization of Classes

Specialization of Classes
Slide Annotated slide Contents Index
References Textbook 

Classes are regarded as types, and specializations as subtypes

Specialization facilitates definition of new classes from existing classes on a sound conceptual basis

References

The class B is a specialization of class A

The concept specialization: If a class B is a specialization of a class A then
  • The instances of B is a subset of the instances of A
  • Operations and variables in A are also present in B
  • Some operations from A may be redefined in B

The extension of class specialization
Slide Annotated slide Contents Index
References Textbook 

The extension of a specialized class B is a subset of the extension of the generalized class A

Reference

The extension of a class A is narrowed when the class is specialized to B

  • The is-a relation

    • A   B-object   is an   A-object

    • There is a is-a relation between class A and B

The is-a relation forms a contrast to the has-a relation

The is-a relation characterizes specialization

The has-a relation characterizes aggregation

References

Example: Bank Accounts
Slide Annotated slide Contents Index
References Textbook 

A specialization hierarchy of bank accounts

Possible extensions of the bank account classes

Example: Bank Accounts in C#
Slide Annotated slide Contents Index
References Textbook 

A CheckAccount is a BankAccount, a SavingsAccount is a BankAccount, and a LotteryAccount is a BankAccount.

On this slide we will preview the mechanisms that are used for programming of the BankAccount specializations

Program: The base class BankAccount.
using System;

public class BankAccount {

   protected double interestRate;
   protected string owner;
   protected decimal balance;

   public BankAccount(string o, decimal b, double ir) {
      this.interestRate = ir;
      this.owner = o; 
      this.balance = b;
   }

   public BankAccount(string o, double ir):
     this(o, 0.0M, ir) {
   }

   public virtual decimal Balance {
     get {return balance;}
   }

   public virtual void Withdraw (decimal amount) {
      balance -= amount;
   }

   public virtual void Deposit (decimal amount) {
      balance += amount;
   }

   public virtual void AddInterests() {
      balance += balance * (Decimal)interestRate;
   }    

   public override string ToString() {
      return owner + "'s account holds " +
            + balance + " kroner";
   }
} 

Program: The class CheckAccount.
using System;

public class CheckAccount: BankAccount {

   public CheckAccount(string o, double ir): 
     base(o, 0.0M, ir) {
   }

   public CheckAccount(string o, decimal b, double ir): 
     base(o, b, ir) {
   }

   public override void Withdraw (decimal amount) {
      balance -= amount;
      if (amount < balance)
         interestRate = -0.10;
   }

   public override string ToString() {
      return owner + "'s check account holds " +
            + balance + " kroner";
   }
} 

Program: The class SavingsAccount.
using System;

public class SavingsAccount: BankAccount {

   public SavingsAccount(string o, double ir): 
     base(o, 0.0M, ir) {
   }

   public SavingsAccount(string o, decimal b, double ir): 
     base(o, b, ir) {
   }

   public override void Withdraw (decimal amount) {
      if (amount < balance)
          balance -= amount;
      else
          throw new Exception("Cannot withdraw");
   }

   public override void AddInterests() {
      balance = balance + balance * (decimal)interestRate 
                        - 100.0M;
   }    

   public override string ToString() {
      return owner + "'s savings account holds " +
            + balance + " kroner";
   }
} 

Program: The class LotteryAccount.
using System;

public class LotteryAccount: BankAccount {

   private static Lottery lottery  = Lottery.Instance(20);

   public LotteryAccount(string o, decimal b): 
     base(o, b, 0.0) {
   }

   public override void AddInterests() {
      int luckyNumber = lottery.DrawLotteryNumber;
      balance = balance + lottery.AmountWon(luckyNumber);
   }    

   public override string ToString() {
      return owner + "'s lottery account holds " +
            + balance + " kroner";
   }
} 

Program: The singleton class Lottery - used by LotteryAccount.
using System;

public class Lottery{

  private static Random rdm = 
          new Random(unchecked((int)DateTime.Now.Ticks));

  private int difficulty;
  private readonly int winningNumber;
  private readonly decimal amountWon;
  private static Lottery uniqueInstance = null;

  private Lottery(int difficulty){
    this.difficulty = difficulty;
    this.winningNumber = rdm.Next(difficulty);
    this.amountWon = 500000.00M;
  }

  public static Lottery Instance(int difficulty){
    if (uniqueInstance == null)
      uniqueInstance = new Lottery(difficulty);
    return uniqueInstance;
  }

  public int DrawLotteryNumber{
    get {return rdm.Next(difficulty);}
  }

  public decimal AmountWon(int luckyNumber){
    decimal res;
    if (WinningNumber(luckyNumber))
       res = amountWon;
    else
       res = 0.0M;
    return res;
  }

  private bool WinningNumber(int n){
    return n == winningNumber;
  }
}

Example: Geometric Shapes
Slide Annotated slide Contents Index
References Textbook 

A specialization hierarchy of polygons

Exercise 7.2. Polygons, Quadrangles and Squares

The purpose of this exercise is to get some experience with programming of classes in a pure specialization hierarchy.

I have programmed the class Polygon. The well-known class Point, which we have seen several times during the course, is used as a building block of class Polygon type. (Notice that class Point has a MidPoint operation and a RotateAroundPoint operation).

The class Polygon has the following public operations:

  • A constructor.
  • Rank: The number of edges of the polygon
  • Circumference: The sum of the edge lengths.

Now program the following specializations of Polygon:

  • Quadrangle
  • Square

For the sake of simplicity you are allowed (but not forced) to assume that the edges of the square are parallel with the x-axis and the y-axis.

Please refer to the Polygon type hierarchy on the accompanying slide.

Quadrangle and Square should support the same operations as Polygon. In particular Quadrangle and Square should support adequate constructors of their own.

It is not necessary to use four points for representation of a square. Discuss this problem and try to find a solution.

Specialization of classes
Slide Annotated slide Contents Index
References Textbook 

  • Objects of specialized classes

    • fulfill stronger conditions (constraints) than objects of generalized classes

      • obey stronger class invariants

    • have simpler and more accurate operations than objects of generalized classes

Reference

Specialization of classes in pure form do not occur very often.

Specialization in combination with extension is much more typical.

The Principle of Substitution
Slide Annotated slide Contents Index
References Textbook 

When specialization is used in pure form the principle of substitution applies

The class B is a specialization of class A

The concept principle of substitution: If B is a subclass of A, it is possible to substitute an given instance of B in place of an instance of A without observable effect

  • Concrete example:

    • It is possible to replace a BankAccount with a CheckAccount

    • It is not, in general, possible to replace a CheckAccount with a BankAccount


Extension of Classes

Extension of Classes
Slide Annotated slide Contents Index
References Textbook 

Classes can both be regarded as types and modules.

Class extension is a program transport and program reusability mechanism.

The class B is a specialization of class A

The concept extension: If class B is an extension of class A then
  • B may add new variables and operations to A
  • Operations and variables in A are also present in B
  • B-objects are not necessarily conceptually related to A-objects

An example of simple extension
Slide Annotated slide Contents Index
References Textbook 

We extend the 2D Point class to a 3D Point class

Program: The class Point2D.
using System;

public class Point2D {
  private double x, y;

  public Point2D(double x, double y){
   this.x = x; this.y = y;
  }

  public double X{
    get {return x;}
  }

  public double Y{
    get {return y;}
  }

  public void Move(double dx, double dy){
    x += dx; y += dy;
  }

  public override string ToString(){
    return "Point2D: " + "(" + x + ", " + y + ")" + ".";
  }
}

Program: The class Point3D which extends class Point3d.
using System;

public class Point3D: Point2D {

  private double z;

  public Point3D(double x, double y, double z):
         base(x,y){
   this.z = z;
  }

  public double Z{
    get {return z;}
  }

  public void Move(double dx, double dy, double dz){
    base.Move(dx, dy);
    z += dz;
  }

  public override string ToString(){
    return "Point3D: " + "(" + X + ", " + Y + ", " + Z + ")" + ".";
  }
}

Program: A client of the classes Point2D and Point3d.
using System;

public class Application{

  public static void Main(){
    Point2D p1 = new Point2D(1.1, 2.2),
            p2 = new Point2D(3.3, 4.4);

    Point3D q1 = new Point3D(1.1, 2.2, 3.3),
            q2 = new Point3D(4.4, 5.5, 6.6);

    p2.Move(1.0, 2.0);
    q2.Move(1.0, 2.0, 3.0);
    Console.WriteLine("{0} {1}", p1, p2);
    Console.WriteLine("{0} {1}", q1, q2);
  }

}

Program: The output from the Client program.
Point2D: (1,1, 2,2). Point2D: (4,3, 6,4).
Point3D: (1,1, 2,2, 3,3). Point3D: (5,4, 7,5, 9,6).

Exercise 7.3. Point3D: A client or a subclass of Point2D?

The purpose of this exercise is to sharpen your understanding of the difference between "being a client of class C" and "being af subclass of class C".

The class Point3D extends Point2D by means of inheritance.

As an alternative, the class Point3D may be implemented as a client of Point2D. In more practical terms this means that the class Point3D has an instance variable of type Point2D. Now implement Point3D as a client of Point2D - such that a 3D point has a 2D point as a part.

Be sure that the class Point3D has the same interface as the version of class Point3D from the course material.

Evaluate the difference between "being a client of" an "extending" class Point2D. Which of the solutions do you prefer?

  • Some observations:

    • A 3D point is not a 2D point

    • Thus, Point3D is not a specialization of Point2D

    • The set of 2D point objects is disjoint from the set of 3D points

The class Point2D was a convenient starting point of the class Point3D

We have reused some data and operations from class Point2D in class Point3D

The intension of class extensions
Slide Annotated slide Contents Index
References Textbook 

The intension of a class extension B is a superset of the intension of the original class A

Reference

The intension of a class A is blown up when the class is extended to B

It is, in general, not possible to characterize the extension of B in relation to the extension of A

Often, the extension of A does not overlap with the extension of B


Inheritance in General

Inheritance
Slide Annotated slide Contents Index
References Textbook 

Inheritance is a mechanism in object-oriented programming languages that facilitates class specialization and class extension

Two classes B and C that inherit from class A

  • The inheritance of B and C from A:

    • Organizes the classes in a hierarchy

    • Provides for some degree of specialization and/or extension of A

    • At program development time, data and operations of A can be reused in B and C without copying and without any duplication in the source program

    • At runtime, instances of class B and C are whole objects, without A parts

Interfaces to clients and subclasses
Slide Annotated slide Contents Index
References Textbook 

Figure. Interfaces between A, B, their client classes, and their subclasses
  1. The client interface of A
  2. The subclass interface between A and its subclass B
  3. The client interface of B
  4. The subclass interface between B and potential subclasses of B

Visibility and Inheritance
Slide Annotated slide Contents Index
References Textbook 

In most object-oriented programming languages it is possible to control the visibility of data and operations in relation to subclasses

  • Common visibility distinctions:

    • Private

      • Visibility limited to the class itself.

      • Instances of a given class can see each others private data and operations

    • Protected

      • Visibility is limited to the class itself and to its subclasses

    • Public

      • No visibility limitations

Class hierarchies and Inheritance
Slide Annotated slide Contents Index
References Textbook 

The inheritance relations among a set of classes define graph structure of classes

This graph structure is, in general, acyclic

Different graph structures among classes

Multiple inheritance and repeated inheritance are allowed in some object-oriented programming languages

Multiple inheritance
Slide Annotated slide Contents Index
References Textbook 

Why should we care about multiple inheritance?

  • Specialization of two or more classes

    • Example: An isosceles right triangle is a isosceles triangle and it is a right triangle

    • Example: There may exists a bank account which is a checking account and it is a savings account

  • Extensions of two or more classes

    • "Program transport" from multiple superclasses

References

Problems with multiple inheritance
Slide Annotated slide Contents Index
References Textbook 

Let us understand the major reason why multiple inheritance is considered problematic

Class B is a subclass of class A

  • Concrete problem

    • In a C object ac: Which x does ac.x refer to?

  • General problems

    • The name clash problem: Does x in C refer to the x in A or the x in B?

    • The combination problem: Can x in A and x in B combined to a single x in C?

    • The selection problem: Do we have means in C to select either x in A or x in B?

    • The replication problem: Is there one or two x pieces in C?


Inheritance in C#

Class Inheritance in C#
Slide Annotated slide Contents Index
References Textbook 

Syntax: A C# class defined as a subclass of given superclass

class-modifier class class-name: super-class-name{
  declarations
}

Program: A class A and its subclass B. Or a class B and its base class A.
class A {}

class B: A {}

The class B inherits from class A

B is said to be a subclass of A, and A a superclass of B. A is also called the base class of B.

The top of the class hierarchy
Slide Annotated slide Contents Index
References Textbook 

The root of the class hierarchy is the class called Object

The methods in class Object are shared between reference types and value types

The overall type hierarchy in C#

Methods in the class Object in C#
Slide Annotated slide Contents Index
References Textbook 

The methods in class Object can be used uniformly on both values and objects in C#

  • Public methods in class Object

    • Equals:

      • obj1.Equals(obj2)    -    Instance method

      • Object.Equals(obj1, obj2)    -    Static method

      • Object.ReferenceEquals(obj1,obj2)    -    Static method

    • obj.GetHashCode()

    • obj.GetType()

    • obj.ToString()

  • Protected methods in class Object

    • obj.Finalize()

    • obj.MemberwiseClone()

References

Inheritance and Constructors
Slide Annotated slide Contents Index
References Textbook 

Constructors are not inherited

Reference

  • Each class in a class hierarchy should have its own constructor(s)

  • The constructor of class C cooperates with constructors in superclasses of C to initialize a new instance of C

  • A constructor in a subclass will always, implicitly or explicitly, refer to a constructor in its superclass

Program: Constructors in class BankAccount.
using System;

public class BankAccount {

   protected double interestRate;
   protected string owner;
   protected decimal balance;

   public BankAccount(string o, decimal b, double ir) {
      this.interestRate = ir;
      this.owner = o; 
      this.balance = b;
   }

   public BankAccount(string o, double ir):
     this(o, 0.0M, ir) {
   }

   public virtual decimal Balance {
     get {return balance;}
   }

   public virtual void Withdraw (decimal amount) {
      balance -= amount;
   }

   public virtual void Deposit (decimal amount) {
      balance += amount;
   }

   public virtual void AddInterests() {
      balance += balance * (Decimal)interestRate;
   }    

   public override string ToString() {
      return owner + "'s account holds " +
            + balance + " kroner";
   }
} 

Program: Constructors in class CheckAccount.
using System;

public class CheckAccount: BankAccount {

   public CheckAccount(string o, double ir): 
     base(o, 0.0M, ir) {
   }

   public CheckAccount(string o, decimal b, double ir): 
     base(o, b, ir) {
   }

   public override void Withdraw (decimal amount) {
      balance -= amount;
      if (amount < balance)
         interestRate = -0.10;
   }

   public override string ToString() {
      return owner + "'s check account holds " +
            + balance + " kroner";
   }
} 

Program: Constructors in class SavingsAccount.
using System;

public class SavingsAccount: BankAccount {

   public SavingsAccount(string o, double ir): 
     base(o, 0.0M, ir) {
   }

   public SavingsAccount(string o, decimal b, double ir): 
     base(o, b, ir) {
   }

   public override void Withdraw (decimal amount) {
      if (amount < balance)
          balance -= amount;
      else
          throw new Exception("Cannot withdraw");
   }

   public override void AddInterests() {
      balance = balance + balance * (decimal)interestRate 
                        - 100.0M;
   }    

   public override string ToString() {
      return owner + "'s savings account holds " +
            + balance + " kroner";
   }
} 

Program: Constructors in class LotteryAccount.
using System;

public class LotteryAccount: BankAccount {

   private static Lottery lottery  = Lottery.Instance(20);

   public LotteryAccount(string o, decimal b): 
     base(o, b, 0.0) {
   }

   public override void AddInterests() {
      int luckyNumber = lottery.DrawLotteryNumber;
      balance = balance + lottery.AmountWon(luckyNumber);
   }    

   public override string ToString() {
      return owner + "'s lottery account holds " +
            + balance + " kroner";
   }
} 

Constructors and initialization order
Slide Annotated slide Contents Index
References Textbook 

The instance variables of a newly allocated object are initialized in a particular order

  • Initialization of a C-object: new C(...)

    • Instance variables in C are initialized (field initializers)

    • Instance variables in superclasses are initialized - most specialized first

    • Constructors of the superclasses are executed - most general first

    • The constructor body of C is executed

Constructors and initialization order: Example
Slide Annotated slide Contents Index
References Textbook 

Instantiation of class C that inherits from class B that inherits from class A

Program: Initializers and constructors of class C.
using System;

public class C: B {
  private int varC1 = Init.InitMe(1, "varC1, initializer in class C"),
              varC2;

  public C (){
    varC2 = Init.InitMe(4, "VarC2, constructor body C");
  }
}

Program: Initializers and constructors of class B.
using System;

public class B: A {
  private int varB1 = Init.InitMe(1, "varB1, initializer in class B"),
              varB2;

  public B (){
    varB2 = Init.InitMe(4, "VarB2, constructor body B");
  }
}

Program: Initializers and constructors of class A.
using System;

public class A {
  private int varA1 = Init.InitMe(1, "varA1, initializer in class A"),
              varA2;

  public A (){
    varA2 = Init.InitMe(4, "VarA2, constructor body A");
  }
}

Program: The class Init and the method InitMe.
using System;

public class Init{
  
  public static int InitMe(int val, string who){
    Console.WriteLine(who);
    return val;
  }

}

Program: A program that instantiates and initializes class C.
using System;

class App{

  public static void Main(){
    C c = new C();
  }
}

Program: The output that reveals the initialization order.
varC1, initializer in class C
varB1, initializer in class B
varA1, initializer in class A
VarA2, constructor body A
VarB2, constructor body B
VarC2, constructor body C

Visibility modifiers in C#
Slide Annotated slide Contents Index
References Textbook 

The visibility of types in assemblies and members in classes are controlled by visibility modifiers

  • Visibility of a type (e.g. a class) in an assembly

    • internal: The type is not visible from outside the assembly

    • public: The type is visible outside the assembly

  • Visibility of members in type (e.g., methods in classes)

    • private: Accessible only in the containing type

    • protected: Accessible in the containing type and in subtypes

    • internal: Accessible in the assembly

    • protected internal: Accessible in the assembly and in the containing type and its subtypes

    • public: Accessible whenever the enclosing type is accessible

Reference

Exercise 7.5. Private Visibility and inheritance

Take a look at the classes shown below:

using System;

public class A{
  private int i = 7;

  protected int F(int j){
   return i + j;
  }
}

public class B : A{
  public void G(){
    Console.WriteLine("i: {0}", i);
    Console.WriteLine("F(5): {0}", F(5));
  }
}

public class Client {
  public static void Main(){
    B b = new B();
    b.G();
  }
}

Answer the following questions before you run the program:

  1. Does the instance of B, created in Main in Client, have an instance variable i?
  2. Is the first call to Console.WriteLine in G legal?
  3. Is the second call to Console.WriteLine in G legal?

Run the program and confirm your answers.

Exercise 7.5. Internal Visibility

The purpose of this exercise is to get some experience with the visibility modifier called internal. Take a look at the slide to which this exercise belongs.

In this exercise, it is recommended to activate the compiler from a command prompt.

Make a namespace N with two classes P and I:

  • P should be public. P should have a static public member p and a static internal member i.
  • I should be internal. I should also have a static public member p and a static internal member i.

Compile the classes in the namespace N to a single assembly, for instance located in the file x.dll.

Demonstrate that the class I can be used in class P. Also demonstrate that P.i can be seen and used in class P.

After this, program a class A, which attempts to use the classes P and I from x.dll. Arrange that class A is compiled separately, to a file y.dll. Answer the following questions about class A:

  1. Can you declare variables of type P in class A?
  2. Can you declare variables of type I in class A?
  3. Can you access P.i and and P.p in A?
  4. Can you access I.i and and I.p in A?

Finally, arrange that class A is compiled together with N.P and N.I to a single assembly, say y.dll. Does this alternative organization affect the answers to the questions asked above?

Inheritance of methods, properties, and indexers
Slide Annotated slide Contents Index
References Textbook 

Methods, properties, and indexers are inherited

  • Methods, properties, and indexers can be redefined in two different senses:

    • Same names and signatures in super- and subclass, closely related meanings (virtual, override)

    • Same names and signatures in super- and subclass, two entirely different meanings (new)

  • A method M in a subclass B can refer to a method M in a superclass A

    • base.M(...)

    • Cooperation, also known as method combination

References

Operators are inherited. A redefined operator in a subclass will be an entirely new operator.

Inheritance of methods: Example.
Slide Annotated slide Contents Index
References Textbook 

Method inheritance in the bank account hierarchy

Program: The base class BankAccount.
using System;

public class BankAccount {

   protected double interestRate;
   protected string owner;
   protected decimal balance;

   public BankAccount(string o, decimal b, double ir) {
      this.interestRate = ir;
      this.owner = o; 
      this.balance = b;
   }

   public BankAccount(string o, double ir):
     this(o, 0.0M, ir) {
   }

   public virtual decimal Balance {
     get {return balance;}
   }

   public virtual void Withdraw (decimal amount) {
      balance -= amount;
   }

   public virtual void Deposit (decimal amount) {
      balance += amount;
   }

   public virtual void AddInterests() {
      balance += balance * (Decimal)interestRate;
   }    

   public override string ToString() {
      return owner + "'s account holds " +
            + balance + " kroner";
   }
} 

Program: The class CheckAccount.
using System;

public class CheckAccount: BankAccount {

   // Instance variables of BankAccount are inherited

   public CheckAccount(string o, double ir): 
     base(o, 0.0M, ir) {
   }

   public CheckAccount(string o, decimal b, double ir): 
     base(o, b, ir) {
   }

   // Method Balance is inherited
   // Method Deposit is inherited
   // Method AddInterests is inherited

   public override void Withdraw (decimal amount) {
      base.Withdraw(amount);
      if (amount < balance)
         interestRate = -0.10;
   }

   public override string ToString() {
      return owner + "'s check account holds " +
            + balance + " kroner";
   }
} 

Program: The class SavingsAccount.
using System;

public class SavingsAccount: BankAccount {

   // Instance variables of BankAccount are inherited

   public SavingsAccount(string o, double ir): 
     base(o, 0.0M, ir) {
   }

   public SavingsAccount(string o, decimal b, double ir): 
     base(o, b, ir) {
   }

   // Method Balance is inherited
   // Method Deposit is inherited

   public override void Withdraw (decimal amount) {
      if (amount < balance)
          base.Withdraw(amount);
      else
          throw new Exception("Cannot withdraw");
   }

   public override void AddInterests() {
      balance = balance + balance * (decimal)interestRate 
                        - 100.0M;
   }    

   public override string ToString() {
      return owner + "'s check account holds " +
            + balance + " kroner";
   }
} 

Program: The class LotteryAccount.
using System;

public class LotteryAccount: BankAccount {

   // Instance variables of BankAccount are inherited

   protected static Lottery lottery  = Lottery.Instance(3);

   public LotteryAccount(string o, decimal b): 
     base(o, b, 0.0) {
   }

   // Method Balance is inherited
   // Method Deposit is inherited
   // Method Withdraw is inherited

   public override void AddInterests() {
      int luckyNumber = lottery.DrawLotteryNumber;
      balance = balance + lottery.AmountWon(luckyNumber);
   }    

   public override string ToString() {
      return owner + "'s lottery account holds " +
            + balance + " kroner";
   }
} 

Program: The class Lottery.
using System;

public class Lottery{

  private static Random rdm = new Random(unchecked((int)DateTime.Now.Ticks));

  private int difficulty;
  private readonly int winningNumber;
  private readonly decimal amountWon;
  private static Lottery uniqueInstance = null;

  private Lottery(int difficulty){
    this.difficulty = difficulty;
    this.winningNumber = rdm.Next(difficulty);
    this.amountWon = 500000.00M;
  }

  public static Lottery Instance(int difficulty){
    if (uniqueInstance == null)
      uniqueInstance = new Lottery(difficulty);
    return uniqueInstance;
  }

  public int DrawLotteryNumber{
    get {return rdm.Next(difficulty);}
  }

  public bool IsWinningNumber(int number){
    return number == winningNumber;
  }

  public decimal AmountWon(int luckyNumber){
    decimal res;
    if (IsWinningNumber(luckyNumber))
       res = amountWon;
    else
       res = 0.0M;
    return res;
  }
}

Exercise 7.6. A subclass of LotteryAccount

On the slide, to which this exercise belongs, we have emphasized inheritance of methods and properties in the bank account class hierarchy. From the web-version of the material there is direct access to the necessary pieces of program.

The LotteryAccount uses an instance of a Lottery object for adding interests. Under some lucky circumstances, the owner of a LotteryAccount will get a substantial amount of interests. In most cases, however, no interests will be added.

There exists a single file which contains the classes BankAccount, CheckAccount, SavingsAccount, Lottery, together with a sample client class.

Program a specialization of the LotteryAccount, called LotteyPlusAccount, with the following redefinitions of Deposit and Withdraw.

  • The Deposit method doubles the deposited amount in case you draw a winning lottery number upon deposit. If you are not lucky, Deposit works as in LottoryAccount, but an administrative fee of 15 kroner will be withdrawn from your LotteryPlusAccount.
  • The Withdraw method returns the withdrawn amount without actually deducting it from the LotteryPlusAccount if you draw a winning lottery number upon withdrawal. If you are not lucky, Withdraw works as in LottoryAccount, and an additional administrative fee of 50 kroner will be withdrawn from the account as well.

Notice that the Deposit and Withdraw methods in LotteryPlusAccount should combine with the method in LotteryAccount (method combination). Thus, use the Deposit and Withdraw methods from LotteryAccount as much as possible when you program the LotteryPlusAccount.

Test-drive the class LotteryPlusAccount from a sample client class.

Overriding and Hiding in C#
Slide Annotated slide Contents Index
References Textbook 

What if a method M in class A also appears in class B?

Program: Two methods M in classes A and B, where B inherits from A.
class A {
  public void M(){}
}

class B: A{
  public void M(){}
}

  • Intended redefinition:
    B.M is intended to redefine A.M - such that B.M is used on B instances

    • A.M must be declared as virtual

    • B.M must be declared to override A.M

  • Accidental redefinition:
    The programmer of class B is not aware of A.M

    • B.M must declare that it is not related to A.M - using the new modifier

Polymorphism. Static and dynamic types
Slide Annotated slide Contents Index
References Textbook 

Polymorphism and appropriate use of dynamic binding is the OOP crown jewels in relation to inheritance

The concept polymorphism: Polymorphism stands for the idea that a variable can refer to objects of several different types
The concept static type: The static type of a variable is the type of variable, as declared
The concept dynamic type: The dynamic type of a variable is type of object to which the variable refers
The concept dynamic binding: Dynamic binding is in effect if the dynamic type of a variable v determines the operation activated by v.op(...)

Dynamic binding is obtained by virtual methods

Static and dynamic types in C#
Slide Annotated slide Contents Index
References Textbook 

Knowledge about the static types of variables and expressions is used for compile-time type checking

Program: Illustration of static and dynamic types.
class A {}
class B: A{}

class Client{
  public static void Main (){
                 // Static type    Dynamic type
    A x;         //    A             -
    B y;         //    B             -

    x = new A(); //    A             A   TRIVIAL
    y = new B(); //    B             B   TRIVIAL

    x = y;       //    A             B   OK - TYPICAL

    y = new A(); //    B             A   Compile time ERROR
                 //                      Cannot implicitly convert type 'A' to 'B'.
    y = x;       //    B             B   Compile time ERROR !
                 //                      Cannot implicitly convert type 'A' to 'B'.
  }
}

Program: Corrections of the errors in the illustration of static and dynamic types.
class A {}
class B: A{}

class Client{
  public static void Main (){
                    // Static type    Dynamic type
    A x;            //    A              -
    B y;            //    B              -

    x = new A();    //    A            A   TRIVIAL
    y = new B();    //    B            B   TRIVIAL

    x = y;          //    A            B   OK - TYPICAL

    y = (B)new A(); //    B            A   RUNTIME ERROR
    y = (B)x;       //    B            B   NOW OK
  }
}

Type test and type conversion in C#
Slide Annotated slide Contents Index
References Textbook 

It is possible to test if the dynamic type of variable v is class type C

There are two ways to convert (cast) one class type to another

  • v is C

    • True if the variable v is of dynamic type C

    • Also true if the variable v is of dynamic type D, where D is a subtype of C

  • (C)v

    • Convert the static type of v to C in the given expression

    • Only possible if the dynamic type of v is C , or a subtype of C

    • If not, an InvalidCastException is thrown

  • v as C

    • Non-fatal variant of (C)v

    • Thus, convert the static type of v to C in the given expression

    • Returns null if the dynamic type of v is not C, or a subtype of C

The typeof operator can be applied on a typename to obtain the corresponding object of class Type

The Object.GetType instance method returns an object of class Type that represents the run-time type of the receiver.

Reference

Examples of type test and type conversion
Slide Annotated slide Contents Index
References Textbook 

Concrete BankAccount examples of type test and type conversions

Systematic use of v is C, (C)v, and v as C on bank account classes

Program: An illustration of dynamic type run-time check with v is C.
using System; 

class App {

  public static void Main(){
  
    BankAccount ba1 = new BankAccount("George", 1000.0M, 0.01),
                ba2 = new CheckAccount("Bill", 2000.0M, 0.01);

    CheckAccount ca = new CheckAccount("John", 2000.0M, 0.01);

    if (ba1 is BankAccount)
      Console.WriteLine("ba1 is BankAccount");          
    else
      Console.WriteLine("ba1 is NOT BankAccount");

    if (ba1 is CheckAccount)
      Console.WriteLine("ba1 is CheckAccount");
    else
      Console.WriteLine("ba1 is NOT CheckAccount");


    if (ba2 is BankAccount)
      Console.WriteLine("ba2 is BankAccount");
    else
      Console.WriteLine("ba2 is NOT BankAccount");

    if (ba2 is CheckAccount)
      Console.WriteLine("ba2 is CheckAccount");
    else
      Console.WriteLine("ba2 is NOT CheckAccount");


    if (ca is BankAccount)
      Console.WriteLine("ca is BankAccount");
    else
      Console.WriteLine("ca is NOT BankAccount");

    if (ca is CheckAccount)
      Console.WriteLine("ca is CheckAccount");
    else
      Console.WriteLine("ca is NOT CheckAccount");

  }

}

Program: Output of dynamic type run-time checking program .
ba1 is BankAccount
ba1 is NOT CheckAccount
ba2 is BankAccount
ba2 is CheckAccount
ca is BankAccount
ca is CheckAccount

Program: An illustration of type casting (C)v.
using System; 

class App {

  public static void Main(){
  
    BankAccount ba1 = new BankAccount("George", 1000.0M, 0.01),
                ba2 = new CheckAccount("Bill", 2000.0M, 0.01),
                baRes1, baRes2, baRes3, baRes4, baRes5, baRes6; 

    CheckAccount ca = new CheckAccount("John", 2000.0M, 0.01);

    baRes1 = (BankAccount)ba1;     // OK.  But b1 is already of static 
                                   //      type BankAccount
    baRes2 = (CheckAccount)ba1;    // Illegal downcasting. Run-time error

    baRes3 = (BankAccount)ba2;     // OK.  But ba2 is already of static
                                   //      type BankAccount
    baRes4 = (CheckAccount)ba2;    // OK because ba2 refers to a CheckAccount

    baRes5 = (BankAccount)ca;      // OK.  Legal upcasting.
    baRes6 = (CheckAccount)ca;     // OK.  But ca is already of static 
                                   //      type CheckAccount
  }

}

Exercise 7.8. Casting of BankAccounts

In the program "An illustration of type casting (C)v" the static types of the variables baRes1 .. baRes6 are BankAccount. Now change the static types of the variables baRes1 .. baRes6 to CheckAccount. This is done simply by declaring baRes1 .. baRes6 as CheckAccounts.

Predict the compilers reaction before you attempt a compilation of the modified program.

Afterwards compile the program and confirm your predictions.

Program: An illustration of type conversion with v as C.
using System; 

class App {

  public static void Main(){
  
    BankAccount ba1 = new BankAccount("George", 1000.0M, 0.01),
                ba2 = new CheckAccount("Bill", 2000.0M, 0.01),
                baRes1, baRes2, baRes3, baRes4, baRes5, baRes6; 

    CheckAccount ca = new CheckAccount("John", 2000.0M, 0.01);

    baRes1 = ba1 as BankAccount; 
    Report(baRes1);
                                   
    baRes2 = ba1 as CheckAccount;    
    Report(baRes2);       // null is reported

    baRes3 = ba2 as BankAccount; 
    Report(baRes3);
                                   
    baRes4 = ba2 as CheckAccount;
    Report(baRes4);

    baRes5 = ca as BankAccount;      
    Report(baRes5);

    baRes6 = ca as CheckAccount;  
    Report(baRes6);
  }

  public static void Report(BankAccount ba){
    if (ba != null)
      Console.WriteLine("{0}", ba);
    else 
      Console.WriteLine("null");
  }

}

Program: Output of dynamic type type conversion program .
George's account holds 1000,0 kroner
null
Bill's check account holds 2000,0 kroner
Bill's check account holds 2000,0 kroner
John's check account holds 2000,0 kroner
John's check account holds 2000,0 kroner

Exercise 7.8. Static and dynamic types

Type conversion with v as T was illustrated with a program on the accompanying slide. The output of the program was confusing and misleading. We want to report the static types of the expressions ba1 as BankAccount, ba1 as CheckAccount, etc. If you access this exercise from the web-version there will be direct links to the appropriate pieces of program.

Explain the output of the program. You can examine the classes BankAccount, CheckAccount, SavingsAccount and LotteryAccount, if you need it.

Modify the program such that the static type of the expressions bai as BanktypeAccount is reported. Instead of

  baRes1 = ba1 as BankAccount; 
  Report(baRes1);

you should activate some method on the expression ba1 as BankAccount which reveals its static type. In order to do so, it is allowed to add extra methods to the bank account classes.

Virtual methods in C#
Slide Annotated slide Contents Index
References Textbook 

A systematic illustration of the modifiers virtual, override, and new

Program: An illustration of virtual and new methods in class A and B.
using System;

class A {
  public void          M( ){Console.WriteLine("M in A");}
  public virtual void  N( ){Console.WriteLine("N in A");}
  public virtual void  O( ){Console.WriteLine("O in A");}
  public void          P( ){Console.WriteLine("P in A");}
}

class B: A{
  public void          M( ){Console.WriteLine("M in B");} // warning
  public override void N( ){Console.WriteLine("N in B");}
  public void          O( ){Console.WriteLine("O in B");} // warning
  public new void      P( ){Console.WriteLine("P in B");}
}

class Client {
  public static void Main(){
    A aa = new A( ),     // aa has static type A, and dynamic type A
      ab = new B( );     // ab has static type A, and dynamic type B
    B b = new B( );      // b  has static type B, and dynamic type B

    aa.N( );   ab.N( );   b.N( );    // The dynamic type controls
    Console.WriteLine( );
    aa.P( );   ab.P( );   b.P( );    // The static type controls
  }
}

Program: An illustration of virtual and new methods in class A and B - Simplified.
using System;

class A {
  public virtual void  N(){Console.WriteLine("N in A");}
  public void          P(){Console.WriteLine("P in A");}
}

class B: A{
  public override void N(){Console.WriteLine("N in B");}
  public new void      P(){Console.WriteLine("P in B");}
}

class Client {
  public static void Main(){
    A aa = new A(),
      ab = new B();
    B b = new B(); 

    aa.N();   ab.N();   b.N();   // The dynamic type controls
    Console.WriteLine();
    aa.P();   ab.P();   b.P();   // The static type controls
  }
}

Program: Output from the program that illustrates virtual and new methods.
N in A
N in B  
N in B

P in A
P in A  
P in B  

Program: An illustration of virtual and new methods - More simplified.
using System;

class A {
  public virtual void  N(){Console.WriteLine("N in A");}
  public void          P(){Console.WriteLine("P in A");}
}

class B: A{
  public override void N(){Console.WriteLine("N in B");}
  public new void      P(){Console.WriteLine("P in B");}
}

class Client {
  public static void Main(){
    A ab = new B();

    ab.N();         // The dynamic type controls. Prints: N in B
    ab.P();         // The static type controls.  Prints: P in A
  }
}

Program: Output from the more simplified program.
N in B
P in A

Virtual methods use dynamic binding

Properties and indexers can be virtual in the same way as methods

Practical use of virtual methods in C#
Slide Annotated slide Contents Index
References Textbook 

A client of different types of bank account classes illustrates a typical use of virtual methods

Reference

Program: Use of virtual bank account methods.
using System;

public class AccountClient{

  public static void Main(){
     BankAccount[] accounts = 
      new BankAccount[5]{
        new CheckAccount("Per",1000.0M, 0.03),
        new SavingsAccount("Poul",1000.0M, 0.03),
        new CheckAccount("Kurt",1000.0M, 0.03),
        new LotteryAccount("Bent",1000.0M),
        new LotteryAccount("Lone",1000.0M)
      };

    foreach(BankAccount ba in accounts){
      ba.AddInterests();  
    }  

    foreach(BankAccount ba in accounts){
      Console.WriteLine("{0}", ba);
    }
  }

}  

Program: Adding interests without use of dynamic binding - AddInterest is not virtual.
using System;

public class AccountClient{

  public static void Main(){
     BankAccount[] accounts = 
      new BankAccount[5]{
        new CheckAccount("Per",1000.0M, 0.03),
        new SavingsAccount("Poul",1000.0M, 0.03),
        new CheckAccount("Kurt",1000.0M, 0.03),
        new LotteryAccount("Bent",1000.0M),
        new LotteryAccount("Lone",1000.0M)
      };

    foreach(BankAccount ba in accounts){
      if (ba is CheckAccount)
        ((CheckAccount)ba).AddInterests();  
      else if (ba is SavingsAccount)
        ((SavingsAccount)ba).AddInterests();  
      else if (ba is LotteryAccount)
        ((LotteryAccount)ba).AddInterests();  
      else if (ba is BankAccount)
        ((BankAccount)ba).AddInterests();  
    }  

    foreach(BankAccount ba in accounts){
      Console.WriteLine("{0}", ba);
    }
  }

}  

Program: Output from the bank account programs.
Per's check account holds 1030,000 kroner
Poul's savings account holds 930,000 kroner
Kurt's check account holds 1030,000 kroner
Bent's lottery account holds 1000,0 kroner
Lone's lottery account holds 1000,0 kroner

The use of virtual methods - and dynamic binding - covers a lot of type dispatching which in naive programs are expressed with if-else chains

Overriding the Equals method in a class
Slide Annotated slide Contents Index
References Textbook 

It is tricky to do a correct overriding of the virtual Equals method in class Object

References

  • Cases to deal with when redefining the Equals method:

    • Comparison with null (false)

    • Comparison with an object of a different type (false)

    • Comparison with ReferenceEquals (true)

    • Comparison of fields in two objects of the same type

  • Other rules when redefining Equals:

    • Must not lead to errors (no exceptions thrown)

    • The implemented equality should be reflexive, symmetric and transitive

  • Additional work:

    • GetHashCode should also be redefined in accordance with Equals

      • If o1.Equals(o2)   then   o1.GetHashCode() == o2.GetHashCode()

    • If you overload the == operator

      • Also overload !=

      • Make sure that o1 == o2 and o1.Equals(o2) return the same result

Program: Equals and GetHashCode Methods in class BankAccount.
using System;
using System.Collections;

public class BankAccount {

   private double interestRate;
   private string owner;
   private decimal balance;
   private long accountNumber;

   private static long nextAccountNumber = 0;
   private static ArrayList accounts = new ArrayList();

   public BankAccount(string owner): this(owner, 0.0) {
   }

   public BankAccount(string owner, double interestRate) {
      nextAccountNumber++;
      accounts.Add(this);
      this.accountNumber = nextAccountNumber;
      this.interestRate = interestRate;
      this.owner = owner; 
      this.balance = 0.0M;
   }   

   public override bool Equals(Object obj){
     if (obj == null)
       return false;
     else if (this.GetType() != obj.GetType())
       return false;
     else if (ReferenceEquals(this, obj))
       return true;
     else if (this.accountNumber == ((BankAccount)obj).accountNumber)
       return true;
     else return false;
   }

   public override int GetHashCode(){
     return (int)accountNumber ^ (int)(accountNumber >> 32);
     // XOR of low orders and high orders bits of accountNumber
     // (of type long) according to GetHashCode API recommendation.
   }

   public decimal Balance () {
      return balance;
   }

   public static long NumberOfAccounts (){
     return nextAccountNumber;
   }

   public static BankAccount GetAccount (long accountNumber){
      foreach(BankAccount ba in accounts)
        if (ba.accountNumber == accountNumber)
           return ba;
      return null;
   }

   public void Withdraw (decimal amount) {
      balance -= amount;
   }

   public void Deposit (decimal amount) {
      balance += amount;
   }

   public void AddInterests() {
      balance = balance + balance * (decimal)interestRate;
   }    

   public override string ToString() {
      return owner + "'s account, no. " + accountNumber + " holds " +
            + balance + " kroner";
   }
} 

Upcasting and downcasting in C#
Slide Annotated slide Contents Index
References Textbook 

Upcasting converts an object of a specialized type to a more general type

Downcasting converts an object from a general type to a more specialized type

A specialization hierarchy of bank accounts

Program: An illustration of upcasting and downcasting.
    BankAccount    ba1,
                   ba2 =   new BankAccount("John", 250.0M, 0.01);
    LotteryAccount la1,
                   la2 =   new LotteryAccount("Bent", 100.0M);

    ba1 = la2;                    // upcasting   - OK
//  la1 = ba2;                    // downcasting - Illegal 
                                  //   discovered at compile time
//  la1 = (LotteryAccount)ba2;    // downcasting - Illegal
                                  //   discovered at run time
    la1 = (LotteryAccount)ba1;    // downcasting - OK 
                                  //   ba1 already refers to a LotteryAccount

Upcasting and downcasting in C#
Slide Annotated slide Contents Index
References Textbook 

Upcasting and downcasting reflect different views on a given object

The object is not 'physically changed' due to upcasting or downcasting

  • Upcasting:

    • Can occur implicitly during assignment and parameter passing

    • A natural consequence of polymorphism and the is-a relation

    • Can always take place

  • Downcasting:

    • Must be done explicitly by use of type casting

    • Can not always take place

Inheritance and Variables
Slide Annotated slide Contents Index
References Textbook 

Variables (fields) are inherited

Variables cannot be virtual

  • A variable can be redefined in the sense:

    • Same name in super- and subclass: two entirely different meanings (new)

Program: An illustration of "non-virtual variable access".
using System;

public class A{
  public int v = 1;
}

public class B: A{
  public new int v = 5;
}

public class App{
  public static void Main(){   // Static type    Dynamic type
    A anA =      new A(),      //     A             A
      anotherA = new B();      //     A             B
    B aB =       new B();      //     B             B
  
    Console.WriteLine(
     "{0}",
       anA.v               // 1
       + anotherA.v        // 1    
       + ((B)anotherA).v   // 5
       + aB.v              // 5
    );
  }
}

Program: Similar illustration with virtual properties.
using System;

public class A{
  public int v = 1;
  
  public virtual int V{
    get{ return v;}
  }   
}

public class B: A{
  public new int v = 5;

  public override int V{
    get{ return v;}
  }   
}

public class App{
  public static void Main(){   // Static type    Dynamic type
    A anA =      new A(),      //     A             A
      anotherA = new B();      //     A             B
    B aB =       new B();      //     B             B
  
    Console.WriteLine(
     "{0}",                // Variable access  Property Access
       anA.V               //   1                1
       + anotherA.V        //   1                5     
       + ((B)anotherA).V   //   5                5
       + aB.V              //   5                5
    );
  }
}

Program: Classes for exercise.
using System;

public class A {
 public int v = 1;
 public virtual int Op1(){return v;}
}

public class B: A{
 public new int v = 2;
 public override int Op1(){return v;}
 public int Op2(){return base.v;}
}

public class Client{

  public static void Main (){
    A a = new B();
    Console.WriteLine("{0}", a.v);
    Console.WriteLine("{0}", a.Op1());

    B b = new B();
    Console.WriteLine("{0}", b.v);
    Console.WriteLine("{0}", b.Op2());
  }

}

Exercise 7.9. Non-virtual variables - Virtual Methods

Take a look at the following program

using System;

public class A {
 public int v = 1;
 public virtual int Op1(){return v;}
}

public class B: A{
 public new int v = 2;
 public override int Op1(){return v;}
 public int Op2(){return base.v;}
}

public class Client{

  public static void Main (){
    A a = new B();
    Console.WriteLine("{0}", a.v);
    Console.WriteLine("{0}", a.Op1());

    B b = new B();
    Console.WriteLine("{0}", b.v);
    Console.WriteLine("{0}", b.Op2());
  }

}

and figure out what it prints.

Explain the behaviour.

We do not normally use public instance variables!


Collected references
Contents Index
Generalization and Specialization Earlier in these notes
Types Earlier in these notes
Extension of a concept Earlier in these notes
Aggregation Earlier in these notes
Specialization Earlier in these notes
Class Invariants Later in these notes
Intension of a concept Earlier in these notes
Bank accounts Earlier in these notes
Geometrical shapes Earlier in these notes
Class Object MSDN2 API Documentation
Cloning Later in these notes
Constructors in C# Earlier in these notes
Namespaces and assemblies Earlier in these notes
Method combination Later in these notes
Redefinition: Overriding or hiding Later in these notes
System.Type MSDN2 API Documentation
The bank account classes Earlier in these notes
HashCode in class Object MSDN2 API Documentation
Equals in class Object MSDN2 API Documentation
Equality in C# Earlier in these notes
Methods in class Object Earlier in these notes

 

Chapter 7: Specialization, Extension, and Inheritance
Course home     Author home     About producing this web     Previous lecture (top)     Next lecture (top)     Previous lecture (bund)     Next lecture (bund)     
Generated: February 7, 2011, 12:16:37