Exercise index of this lecture   Alphabetic index   Course home   

Exercises and solutions
Specialization, Extension, and Inheritance


7.1   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.

Solution

My solution can be seen below. Square is implemented in a general way.

My solution contains two versions of Square. The last, SimpleSquare, has its own minimalistic representation of a Square. It worth noticing that the constructor of SimpleSquare calls a constructor in Polygon which allocates only minimal space to the general polygon. My solution starts with class Polygon and class Line.

using System;

public class Polygon {

  private Point[] corners;
  
  public Polygon(params Point[] corners){
    this.corners = new Point[corners.Length];

    // Copy corners into instance variable:
    for(int i = 0; i < corners.Length; i++)
      this.corners[i] = new Point(corners[i]);
  }

  // Used by specializations that take over the
  // representation of specialized polygons
  protected Polygon(){
    this.corners = new Point[0];         
  }

  private Line[] Edges(){
    Line[] res = new Line[corners.Length];
    int Lgt = corners.Length;
    for (int i = 0; i < Lgt - 1; i++)
       res[i] = new Line(corners[i], corners[i+1]);
    res[Lgt-1] = new Line(corners[Lgt-1], corners[0]);
    return res;
  }

  public virtual double Circumference(){
    double res = 0;
    foreach(Line ln in this.Edges())
      res += ln.Length();
    return res; 
  }

  public virtual int Rank{
    get {
      return corners.Length;}
  }

}

internal class Line{

 private Point p1, p2;
 
 public Line(Point p1, Point p2){
   this.p1 = new Point(p1);
   this.p2 = new Point(p2);
 }

 public double Length(){
   return Math.Sqrt(
            Square(p1.X - p2.X) + 
            Square(p1.Y - p2.Y));
 }

 private static double Square(double a){
   return a * a;
 }
}    



public class Quadrangle: Polygon {
 
  // Used by specializations that take over the
  // representation of specialized polygons
  protected Quadrangle():base(){
  }

  public Quadrangle(Point p1, Point p2, Point p3, Point p4):
    base(p1, p2, p3, p4){
  }

}

public class Square: Quadrangle {

  // Construct a square with the two opposing corners (points on the diagonal).
  public Square(Point p1, Point p2):
    base(p1, p1.RotateAroundPoint(p1.MidPoint(p2), Math.PI/2),
         p2, p2.RotateAroundPoint(p1.MidPoint(p2), Math.PI/2)){
  }
}

public class SimpleSquare: Quadrangle {

  // A simple and specific square representation
  Point p1, p2;

  public SimpleSquare(Point p1, Point p2): 
    base(){
    this.p1 = new Point(p1);
    this.p2 = new Point(p2);
  }

  public override int Rank{
    get {
      return 4;}
  }
    
  public override double Circumference(){
   double diagLength =
       Math.Sqrt( 
        (p1.X - p2.X) * (p1.X - p2.X) +
        (p1.Y - p2.Y) * (p1.Y - p2.Y));

      return (diagLength / Math.Sqrt(2)) * 4;
  }

}



public class App {

  public static void Main(){
    Polygon pol = new Polygon(Point.MakeRectangularPoint(0,0), Point.MakeRectangularPoint(5,0),
                              Point.MakeRectangularPoint(5,5), Point.MakeRectangularPoint(0,5));

    Quadrangle quad = new Quadrangle(Point.MakeRectangularPoint(0,0), Point.MakeRectangularPoint(5,0),
                              Point.MakeRectangularPoint(5,5), Point.MakeRectangularPoint(0,5));

    Square sq = new Square(Point.MakeRectangularPoint(0,0), Point.MakeRectangularPoint(1,0));
    SimpleSquare sr = new SimpleSquare(Point.MakeRectangularPoint(0,0), Point.MakeRectangularPoint(1,0));

    Console.WriteLine("{0}", pol.Circumference());
    Console.WriteLine("{0}", quad.Circumference());
    Console.WriteLine("Circumference: {0}. Rank: {1}", sq.Circumference(), sq.Rank);
    Console.WriteLine("Circumference: {0}. Rank: {1}", sr.Circumference(), sr.Rank);
  }
} 


7.2   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?

Solution

The new Point3D class is here

using System;

public class Point3D {

  private Point2D point2DPart;
  private double z;

  public Point3D(double x, double y, double z){
   point2DPart = new Point2D(x,y);
   this.z = z;
  }

  public double X{
    get {return point2DPart.X;}
  }

  public double Y{
    get {return point2DPart.Y;}
  }

  public double Z{
    get {return z;}
  }

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

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

When the following client program runs

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);
  }

}

the output is

Point2D: (1,1, 2,2). Point2D: (4,3, 6,4).
Point3D: (1,1, 2,2, 3,3). Point3D: (5,4, 7,5, 9,6).

which is identical to the output from the accompanying slide.

It requires a bit more work to implement Point3D by aggregation instead of by extension of class Point2D.


7.3   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.

Solution

The program that we discuss is the following:

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();
  }
}

An instance of B has an instance variable i, inherited from class A, but i is not visible in B.

The method F from class A is also inherited by class B. F is protected in A, and therefore it is visible and applicable in the method G.

The first WriteLine in G is illegal, because i is invisible in class B. The second WriteLine is OK, because F is visible in class B.

In order to compile the program, you must eliminate the first WriteLine in G.

As expected, the output of the modified program is

F(5): 12


7.4   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?

Solution

The namespace N with classes P and I are here:

namespace N{

  public class P{
    internal static int i = 5;
    public static int p = 6;
    private I anI;
  }
  
  internal class I{
    internal static int i = P.i;
    public static int p = P.p; 
  }

}

The class A is here:

using N;
class A {

  public static void M(){
    P aP;
//  I aI;   // Not accessible

//  int x = P.i;  // Not visible outside x.dll
    int y = P.p;

//  int z = I.i;  // Not visible outside x.dll
//  int w = I.p;  // Not visible outside x.dll
  }

}

Here are the answers to the questions:

  1. Can you declare variables of type P in class A? Yes. The class P is public in its assembly.
  2. Can you declare variables of type I in class A? No. The class I is internal in its assembly. Therefore we cannot use I from another assembly.
  3. Can you access P.i and and P.p in A? p is public in P, and therefore it can be used from A. i internal in P, and therefore it cannot be used from another assembly.
  4. Can you access I.i and and I.p in A? No. None of the members in I can be seen outside its assembly. Notice that this is even the case for I.p.

If class A and the classes in the namespace N are compiled together, to a single assembly, all the visibility problems vanish. This compilation can be done with:

  csc /t:library /out:y.dll n.cs aa.cs


7.5   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.

Solution

Here are all the classes in my solution:

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;
  }
}


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";
   }
} 



public class LotteryAccount: BankAccount {

   // Instance variables 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";
   }
} 


public class LotteryPlusAccount: LotteryAccount {

  public LotteryPlusAccount(string owner, decimal balance):
    base(owner,balance){
  }

  public override void Deposit(decimal amount){
    int luckyNumber = lottery.DrawLotteryNumber;
    if (lottery.IsWinningNumber(luckyNumber))
       base.Deposit(amount*2);
    else base.Deposit(amount-15);
  }

  public override void Withdraw(decimal amount){
    int luckyNumber = lottery.DrawLotteryNumber;
    if (lottery.IsWinningNumber(luckyNumber))
       base.Withdraw(0);
    else base.Withdraw(amount + 50);
  }

}

public class AccountClient{

  public static void Main(){
     BankAccount accNormal = new BankAccount("Maria", 1000, 0.1),
                 accPlus = new LotteryPlusAccount("Maria", 1000);


     for(int i = 1; i < 20; i++){
       accPlus.Deposit(100);
       Console.WriteLine("{0}", accPlus);

       accPlus.Withdraw(100);
       Console.WriteLine("{0}\n", accPlus);
    }
  }

}  
    
  

The following sample output from the program illustrates the case where difficulty in Lottery is 3 (one out of three wins):

Maria's lottery account holds 1200 kroner
Maria's lottery account holds 1050 kroner

Maria's lottery account holds 1135 kroner
Maria's lottery account holds 985 kroner

Maria's lottery account holds 1185 kroner
Maria's lottery account holds 1035 kroner

Maria's lottery account holds 1120 kroner
Maria's lottery account holds 970 kroner

Maria's lottery account holds 1170 kroner
Maria's lottery account holds 1020 kroner

Maria's lottery account holds 1105 kroner
Maria's lottery account holds 955 kroner

Maria's lottery account holds 1040 kroner
Maria's lottery account holds 890 kroner

Maria's lottery account holds 975 kroner
Maria's lottery account holds 825 kroner

Maria's lottery account holds 1025 kroner
Maria's lottery account holds 875 kroner

Maria's lottery account holds 960 kroner
Maria's lottery account holds 810 kroner

Maria's lottery account holds 895 kroner
Maria's lottery account holds 895 kroner

Maria's lottery account holds 1095 kroner
Maria's lottery account holds 1095 kroner

Maria's lottery account holds 1295 kroner
Maria's lottery account holds 1145 kroner

Maria's lottery account holds 1230 kroner
Maria's lottery account holds 1080 kroner

Maria's lottery account holds 1165 kroner
Maria's lottery account holds 1015 kroner

Maria's lottery account holds 1215 kroner
Maria's lottery account holds 1065 kroner

Maria's lottery account holds 1150 kroner
Maria's lottery account holds 1000 kroner

Maria's lottery account holds 1200 kroner
Maria's lottery account holds 1200 kroner

Maria's lottery account holds 1400 kroner
Maria's lottery account holds 1400 kroner


7.6   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.

Solution

Here is the program we deal with. The comments explain each of the six cases.

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 caRes1, caRes2, caRes3, caRes4, caRes5, caRes6; 

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

//    caRes1 = (BankAccount)ba1;     // Cannot implicitly convert type 'BankAccount' to 'CheckAccount'
      caRes2 = (CheckAccount)ba1;    // Illegal downcasting. Run-time error. Compiles

//    caRes3 = (BankAccount)ba2;     // Cannot implicitly convert type 'BankAccount' to 'CheckAccount'.
      caRes4 = (CheckAccount)ba2;    // OK because ba2 refers to a CheckAccount

//    caRes5 = (BankAccount)ca;      // Cannot implicitly convert type 'BankAccount' to 'CheckAccount'.
      caRes6 = (CheckAccount)ca;     // OK because ca is a CheckAccount
  }

}



7.7   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.

Solution

The cause of confusion is that the method Report (implicitly) uses the virtual method ToString to print out the accounts. Therefore the dynamic types - and not the static types - are reported.

We can add a non-virtual method, StaticTypeString, to each of the bank account classes in order to get access to the static type of expressions of the form bai as BanktypeAccount.

Here is the modified program

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);

    Report((ba1 as BankAccount).StaticTypeString);
                                   
//    Report((ba1 as CheckAccount).StaticTypeString);    // null

    Report((ba2 as BankAccount).StaticTypeString);
                                   
    Report((ba2 as CheckAccount).StaticTypeString);

    Report((ca as BankAccount).StaticTypeString);

    Report((ca as CheckAccount).StaticTypeString);
  }

  public static void Report(string str){   // Report is, in reality, just Console.WriteLine(str)
    if (str != null)
      Console.WriteLine("{0}", str);
    else 
      Console.WriteLine("null");
  }

}


Here are the modified bank account classes. Notice in particular the non-virtual methods StaticTypeString, marked with new throughout the BankAccount class hierarchy:

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";
   }

   public string StaticTypeString {
    get{
      return "BankAccount";
    }
   }
} 

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";
   }

   public new string StaticTypeString {
    get{
      return "CheckAccount";
    }
   }
} 

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";
   }

   public new string StaticTypeString {
     get{
      return "LotteryAccount";
     }
   }
} 

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 check account holds " +
            + balance + " kroner";
   }

   public new string StaticTypeString {
    get{
      return "SavingsAccount";
    }
   }
} 

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 (luckyNumber == winningNumber)
       res = amountWon;
    else
       res = 0.0M;
    return res;
  }
}


7.8   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.

Solution

The program prints

  1
  2
  2
  1

The first output line reflects that instance variables are non-virtual. The static type of the variable a controls the result.

The second output line reflects that the method Op1 is virtual. Op1 in class B accesses v in B.

The third output line shows, like the first one, that instance variables are non-virtual.

The fourth output line shows that the variable v from A is present in an instance of class B.


Generated: Monday February 7, 2011, 12:17:06