Chapter 3
Classes and Objects

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


Abstract
Previous lecture Next lecture
Index References Contents
In this lecture we will introduce all the fundamental topics in object-oriented programming, which are centered around classes and objects. This includes creation of objects, access to objects, comparison of objects, and object copying.


Classes: An Initial Example

The Die Class
Slide Annotated slide Contents Index
References Textbook 
We start with an example: The class Die. This example illustrates many basic ideas behind classes and objects.

To be concrete we start by looking at a complete class written in C#

Program: The class Die. The class Die encapsulates a couple of variables (numberOfEyes and randomNumberSupplier). It also implements a number of methods that allow us to use a die. The most important operation is Toss.
using System;

public class Die {
  private int numberOfEyes;                
  private Random randomNumberSupplier;     
  private const int maxNumberOfEyes = 6;   

  public Die(){  
    randomNumberSupplier = new Random(unchecked((int)DateTime.Now.Ticks));
    numberOfEyes = NewTossHowManyEyes();
  }   
    
  public void Toss(){  
    numberOfEyes = NewTossHowManyEyes();
  }

  private int NewTossHowManyEyes (){  
    return randomNumberSupplier.Next(1,maxNumberOfEyes + 1);
  }

  public int NumberOfEyes() {  
    return numberOfEyes;       
  }

  public override String ToString(){   
    return String.Format("[{0}]", numberOfEyes);   
  }
}     

Program: A program that tosses three dice. A program that constructs and tosses a number of dies.
using System;

class diceApp {

  public static void Main(){

    Die d1 = new Die(),
        d2 = new Die(),
        d3 = new Die();

    for(int i = 1; i < 10; i++){
      Console.WriteLine("Die 1: {0}", d1);  // Implicitly
      Console.WriteLine("Die 2: {0}", d2);  // calls
      Console.WriteLine("Die 3: {0}", d3);  // ToString in Die
      d1.Toss();  d2.Toss();  d3.Toss(); 
    }

 }
}

Program: Sample program output. What is printed when we run the program shown above.
Die 1: [1]
Die 2: [1]
Die 3: [1]
Die 1: [2]
Die 2: [2]
Die 3: [2]
Die 1: [3]
Die 2: [3]
Die 3: [3]
Die 1: [4]
Die 2: [4]
Die 3: [4]
Die 1: [3]
Die 2: [3]
Die 3: [3]
Die 1: [2]
Die 2: [2]
Die 3: [2]
Die 1: [3]
Die 2: [3]
Die 3: [3]
Die 1: [2]
Die 2: [2]
Die 3: [2]
Die 1: [1]
Die 2: [1]
Die 3: [1]

Program: The class Die in the namespace Game.
using System;

namespace Game {
  public class Die {
    private int numberOfEyes;
    private Random randomNumberSupplier; 
    private const int maxNumberOfEyes = 6;
  
    public Die(){
      randomNumberSupplier = new Random(unchecked((int)DateTime.Now.Ticks));
      numberOfEyes = NewTossHowManyEyes();
    }   
      
    public void Toss(){
      numberOfEyes = NewTossHowManyEyes();
    }
  
    private int NewTossHowManyEyes (){
      return randomNumberSupplier.Next(1,maxNumberOfEyes + 1);
    }
  
    public int NumberOfEyes() {
      return numberOfEyes;
    }
  
    public override String ToString(){
      return String.Format("[{0}]", numberOfEyes);
    }
  }
}

Program: A client of Die from namespace Game.
using System;
using Game;

class diceApp {

  public static void Main(){

    Die d1 = new Die(),
        d2 = new Die(),
        d3 = new Die();

    for(int i = 1; i < 10; i++){
      Console.WriteLine("Die 1: {0}", d1);  // Implicitly
      Console.WriteLine("Die 2: {0}", d2);  // calls
      Console.WriteLine("Die 3: {0}", d3);  // ToString in Die
      d1.Toss();  d2.Toss();  d3.Toss(); 
    }

 }
}

Program: A slightly more general Die class. This version of class Die is not limited to six sides. It can have an arbitrary number of sides.
using System;

public class Die {
  private int numberOfEyes;
  private Random randomNumberSupplier; 
  private readonly int maxNumberOfEyes;

  public Die (): this(6){}  

  public Die (int maxNumberOfEyes){
    randomNumberSupplier = 
      new Random(unchecked((int)DateTime.Now.Ticks));
    this.maxNumberOfEyes = maxNumberOfEyes;
    numberOfEyes = NewTossHowManyEyes();
  }   
    
  public void Toss (){
    numberOfEyes = NewTossHowManyEyes();
  }

  private int NewTossHowManyEyes (){
    return randomNumberSupplier.Next(1,maxNumberOfEyes + 1);
  }

  public int NumberOfEyes() {
    return numberOfEyes;
  }

  public override String ToString(){
    return String.Format("Die[{0}]: {1}", maxNumberOfEyes, numberOfEyes);
  }
}

Reference

Exercise 3.2. Yahtzee

Write a very simple Yahtzee program based on the Die class. Yahtzee is played by use of five dice that are tossed simultaneously. The players are supposed to fill out a table of results. The table should allow registration of ones, ..., sixes, three-of-a-kind, four-of-a-kind, full-house, small-straight, large-straight, yahtzee, and chance. See wikipedia for more details and inspiration.

Be sure to use the version of the Die class that shares the Random class with the other dice. This version of the Die class is produced in another exercise in this lecture.

This program is for a single player, who tosses the five dice repeatedly. Each time the five dice are tossed a table cell is filled. No re-tossing is done with less than five dice. The first category that fits a given toss is filled. (Try yahtzee first, Chance last). Keep it simple!

You may consider to write a YahtzeeTable class which represents the single user table used to register the state of the game. Consider the interface and operations of this class.

The Die class is a template or blueprint from which we can create an arbitrary number of die objects

Read more about class Die in the text book version of this material.

Clients and Servers
Slide Annotated slide Contents Index
References Textbook 
A client uses services (operations) of other object. A server provides services (operations) to other objects. The same object can both act as a client and a server.

In a dice game the objects act, in turn, as client and servers.

Interacting Game, Die, and Random objects. The Game object is a client of the Die objects, which in turn are clients of the Random objects.

  • Client and servers

    • The Game object is a client of a number of Die objects

    • A given Die object is a client of a Random object

    • In turn, a Die object act as a server for the Game object, and Random objects act as servers for Die objects

Read more about clients and servers in the text book version of this material.

Message Passing
Slide Annotated slide Contents Index
References Textbook 
At the conceptual level, we often talk about message passing in between object. At the underlying, technical level this covers activation of operations - procedure or function calls - on objects.

As a metaphor, we pretend that objects communicate by means of message passing

Interacting Game, Die, and Random objects

  • Message passing

    • We often prefer to think of the interaction between objects as message passing.

    • The receiver of an object locates a procedure or a function which can answer the message - method lookup

    • A result may be sent back from the receiver to the sender of the message.

The result sent back is an integral part of the original message - not a new message in its own right.

Read more about message passing in the text book version of this material.


Classes

Classes
Slide Annotated slide Contents Index
References Textbook 
Encapsulation and control of visibility count as the most important ideas of object-oriented programming.

The concept class: A class encapsulates data and operations that belong together, and it controls the visibility of both data and operations. A class can be used as a type in the programming language

A class and its interface to other classes. The interface is often called the client interface. In this illustration the operations Op1, Op2, Op3, and Op4 form the client interface of the class.

The visible parts of a class constitute the interface of the class, as seen by client classes

The interface - or the client interface - make up those parts of the class that can be used from client classes. The non-interface parts of the class are private to the class.

Exercise 3.3. Time Classes

This is not a programming exercise, but an exercise which asks you to consider data and operations of classes related to time.

Time is very important in our everyday life. Therefore, many of the programs we write somehow deal with time.

Design a class PointInTime, which represents a single point in time. How do we represent a point in time? Which variables (data) should be encapsulated in the class? Design the variables in terms of their names and types.

Which operations should constitute the client interface of the class? Design the operations in terms of their names, formal parameters (and their types), and the types of their return values.

Can you imagine other time-related classes than PointInTime?

Avoid looking at the time-related types in the C# library before you solve this exercise. During the course we will come back the time related types in C#.

Read more about classes in the text book version of this material.

Visibility - the Iceberg Analogy
Slide Annotated slide Contents Index
References Textbook 
We present two very useful analogies of class visibilities: Icebergs and firewalls. On this slide, the iceberg analogy is in focus. The tip of the iceberg corresponds to the client interface. The part of the iceberg below the surface correspond to the private parts of the class.

A class can be seen as an iceberg: Only a minor part of it should be visible from the outside. The majority of the class details should be hidden.

An Iceberg. Only a minor fraction of the iceberg is visible above water. In the same way, only a small part of the details of a class should be visible from other classes.

Clients of a class C cannot directly depend on hidden parts of C.

Thus, the invisible parts in C can more easily be changed than the parts which constitute the interface of the class.

The details you cannot see, cannot be used directly in other classes. Therefore it is much easier to change the private part of the class than the interface part of the class. This is important because many classes are likely to change a lot during their lifetimes.

Read more about the iceberg analogy in the text book version of this material.

Visible and Hidden aspects
Slide Annotated slide Contents Index
References Textbook 
Only the name of the class and the headers (signatures) of some selected operations are visible to client classes. The data (variables) of the class should always be private.

Which aspects of a class are hidden, and which aspects are visible from outside the class?

  • Visible aspects

    • The name of the class

    • The signatures of selected operations: The interface of the class

  • Hidden aspects

    • The representation of data

    • The bodies of operations

    • Operations that solely serve as helpers of other operations

Program: The class Die - aspects visible to clients emphasized. On this illustration, the purple aspects correspond to the tip of the iceberg. Please take a careful look at it and be sure to understand the analogy.
using System;

public class Die {
  private int numberOfEyes;                
  private Random randomNumberSupplier;     
  private const int maxNumberOfEyes = 6;   

  public Die(){  
    randomNumberSupplier = new Random(unchecked((int)DateTime.Now.Ticks));
    numberOfEyes = NewTossHowManyEyes();
  }   
    
  public void Toss(){  
    numberOfEyes = NewTossHowManyEyes();
  }

  private int NewTossHowManyEyes (){  
    return randomNumberSupplier.Next(1,maxNumberOfEyes + 1);
  }

  public int NumberOfEyes() {  
    return numberOfEyes;       
  }

  public override String ToString(){   
    return String.Format("[{0}]", numberOfEyes);   
  }
}     

Read more about visibility in the text book version of this material.

Program modification - the Fire Analogy
Slide Annotated slide Contents Index
References Textbook 
A fire correspond to a modification of a class. We are interested in limiting the consequences of the fire. In the world of OOP, encapsulation and visibility control have the same effect as firewalls in a large building.

A minor modification of a program may spread as a fire throughout the program.

Such a fire may ruin most of the program in the sense that major parts of the program may need to be reprogrammed.

Figure. A house - with firewalls - on fire. The fire is not likely to spread to other apartments because of the solid firewalls.

The use of firewalls prevents the spread of a fire.

Similarly, encapsulation and visibility control prevent program modifications from having global consequences.

Read more about the firewall analogy in the text book version of this material.

Representation Independence
Slide Annotated slide Contents Index
References Textbook 
On this page we will illustrate the principle of representation independence. It is done by discussion of a class Point which violates the principle. The class Point has public data representation (rectangular x and y coordinates). We assume that we need to change the rectangular coordinates to so-called polar coordinates (radius and angle). The consequences of this modification of the program is studied via the exercise.

Representation independence: Clients of the class C should not be affected by changes of C's data representation

Program: A Point class with public instance variables - NOT Recommended .
// A very simple point with public data representation.
// NOT RECOMMENDED because of public data representation.

using System;

public class Point {
  public double x, y;  

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

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

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

Program: A Client of Point.
// A client of Point that instantiates three points and calculates
// the circumference of the implied triangle.

using System;

public class Application{

  public static Point PromptPoint(string prompt){
    double x, y;
    Console.WriteLine(prompt);
    x = double.Parse(Console.ReadLine());
    y = double.Parse(Console.ReadLine());
    return new Point(x,y);
  }

  public static void Main(){
    Point p1, p2, p3;
    double p1p2Dist, p2p3Dist,  p3p1Dist, circumference;

    p1 = PromptPoint("Enter first point");
    p2 = PromptPoint("Enter second point");
    p3 = PromptPoint("Enter third point");

    p1p2Dist = Math.Sqrt((p1.x - p2.x) * (p1.x - p2.x) +    
                         (p1.y - p2.y) * (p1.y - p2.y));    
    p2p3Dist = Math.Sqrt((p2.x - p3.x) * (p2.x - p3.x) +    
                         (p2.y - p3.y) * (p2.y - p3.y));    
    p3p1Dist = Math.Sqrt((p3.x - p1.x) * (p3.x - p1.x) + 
                         (p3.y - p1.y) * (p3.y - p1.y));

    circumference = p1p2Dist + p2p3Dist + p3p1Dist;

    Console.WriteLine("Circumference: {0} {1} {2}: {3}", 
                       p1, p2, p3, circumference);

    Console.ReadLine();
  }

}

Program: A version of class Point modified to use polar coordinates - NOT Recommended.
// A very simple class point with public data representation.
// An incomplete sketch.
// This version uses polar representation.
// NOT RECOMMENDED because of public data representation.

using System;

public class Point {
  public double radius, angle;  
                                
  public Point(double x, double y){  
    radius = ...                     
    angle = ...                      
  }

  public void Move(double dx, double dy){
    radius = ...
    angle = ...
  }

  public void Rotate(double angle){  
    this.angle += angle;             
  }

  public override string ToString(){
    ...
  }
}

Exercise 3.4. Public data representation

In the accompanying Point and Point client classes the data representation of Point is available to the client class. This may be tempting for the programmer, because we most likely wish to make the x and y coordinates of points available to clients.

Why is it a bad solution? It is very important that you can express and explain the problem to fellow programmers. Give it a try!

Now assume that we are forced (by the boss) to change the data representation of Point. As a realistic scenario, we may introduce polar coordinates instead of the rectangular x and y coordinates. Recall that polar coordinates consist of a radius and an angle (in radians or degrees).

What will happen to client classes, such as this client, when this change is introduced? Is it an easy or a difficult modification to the given client class? Imagine that in a real-life situation we can have thousands of similar lines of code in client programs that refer to x and y coordinates.

Rewrite selected parts of class Point such that the client "survives" the change of data representation. In your solution, the instance variables should be private in the Point class. Are you able to make a solution such that the client class should not be changed at all?

The private static methods at the bottom of this version of class Point will most likely be helpful. In addition, this version of class Point serves as an initial (but incomplete and somewhat problematic) template of the Point class that you are supposed to write.

The client class of Point calculates the distances between pairs of points. This is not a good idea because far too many details occur repeatedly in the client. Suggest a reorganization and implement it.

Encapsulated data should always be hidden and private within the class

Read more about representation independence in the text book version of this material.

Classes in C#
Slide Annotated slide Contents Index
References Textbook 
At this page we take a look at (a simplified version of) the syntactic composition of a class in C#.

Syntax: The syntactic composition of a C# Class. This is not the whole story. There are other members than variables, constructors and methods. Notice also that it is NOT required that variables come before constructors and that constructors come before methods.

class-modifiers class class-name{
  variable-declarations
  constructor-declarations
  method-declarations
}

  • Members: Class constituents are called members

    • Variables, constructors, methods

    • Other class members will be discussed in coming lectures

  • Visibility: Members may be private or public

    • The default visibility is private

    • Do not rely on default visibility!

  • Ordering: There is no prescribed ordering among members

    • A given coding style typically recommends some particular ordering.

Reference

Read more about classes in C# in the text book version of this material.

Overview of members in classes
Slide Annotated slide Contents Index
References 
Both variables and methods come in two different flavors: instance related and class related. Class related variables and methods are known as static members in C#.

The most important members of a class are variables and methods.

  • Instance variable

    • Defines state that is related to each individual object

  • Class variable

    • Defines state that is shared between all objects

  • Instance method

    • Activated on an object. Can access both instance and class variables

  • Class method

    • Accessed via the class. Can only access class variables

Class variables and class methods are marked with the modifier static.

No modifier is used for instance variables and instance methods.

On the following four pages we will study instance variables, instance methods, class variables and class methods in relatively great detail. If you do not need this level of detail can can skip these pages, and continue here.

Instance Variables
Slide Annotated slide Contents Index
References Textbook 
The individual state of objects are contained in instance variables. We illustrate instance variables by the owner, balance and interest rate of bank accounts. The exercise at the bottom of this page explores the visibility of the private instance across two or more instances of class BankAccount.

The concept instance variable: An instance variable defines a piece of data in the class. Each object, created as an instance of the class, holds a separate copy of the instance variables.

Program: Instance variables in the class BankAccount.
using System;

public class BankAccount {

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

   public BankAccount(string owner) {
      this.interestRate = 0.0;
      this.owner = owner; 
      this.balance = 0.0M;
   }

   public BankAccount(string owner, double interestRate) {
      this.interestRate = interestRate;
      this.owner = owner; 
      this.balance = 0.0M;
   }

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

   public decimal Balance () {
      return balance;
   }

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

Program: Creation of three bank accounts.
using System;

public class BankAccountClient {

  public static void Main(){
    BankAccount a1 = new BankAccount("Kurt", 0.02),
                a2 = new BankAccount("Bent", 0.03),
                a3 = new BankAccount("Thomas", 0.02);

    a1.Deposit(100.0M);
    a2.Deposit(1000.0M); a2.AddInterests();
    a3.Deposit(3000.0M); a3.AddInterests();

    Console.WriteLine(a1);   // 100 kr.
    Console.WriteLine(a2);   // 1030 kr.
    Console.WriteLine(a3);   // 3060 kr.
  }

}

Program: Output from the BankAccount client program.
Kurt's account holds 100 kroner
Bent's account holds 1030 kroner
Thomas's account holds 3060 kroner

Three objects of class BankAccount, each holding three instance variables interestRate, owner, and balance. The values of variables are determined by the bank account transactions that we programmed in the class BankAccountClient. The state of the variables is shown relative to the three WriteLine calls.

Exercise 3.5. How private are private instance variables?

The purpose of this exercise is to find out how private private instance variables are in C#.

Given the BankAccount class. Now modify this class such that each bank account has a backup account. For the backup account you will need a new (private) instance variable of type BankAccount. Modify the Withdraw method, such that if there is not enough money available in the current account, then withdraw the money from the backup account. As an experiment, access the balance of the backup account directly, in the following way:

   backupAccount.balance -= ... 

Is it possible to modify the private state of one BankAccount from another BankAccount? Discuss and explain your findings. Are you surprised?

Read more about instance variables in the text book version of this material.

Instance Methods
Slide Annotated slide Contents Index
References Textbook 
An instance method is always activated on an object. An instance method can can access instance variables as well as class variables.

The concept instance method: An instance method is an operation in a class that can read and/or modify one or more instance variables.

  • An instance method M in a class C

    • must be activated on an object which is an instance of C

    • is activated by object.M(...) from outside C

    • is activated by this.M(...) or just M(...) inside C

    • can access all members of C

Reference

Program: The BankAccount Class. In this version of class BankAccount we have emphasized five instance methods.
using System;

public class BankAccount {

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

   public BankAccount(string owner) {
      this.interestRate = 0.0;
      this.owner = owner; 
      this.balance = 0.0M;
   }

   public BankAccount(string owner, double interestRate) {
      this.interestRate = interestRate;
      this.owner = owner; 
      this.balance = 0.0M;
   }

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

   public decimal Balance () {
      return balance;
   }

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

Program: Selected messages to BankAccount methods from a client class. The activation of the methods represent messages from Main to individual bank account objects.
using System;

public class BankAccountClient {

  public static void Main(){
    BankAccount a1 = new BankAccount("Kurt", 0.02),
                a2 = new BankAccount("Bent", 0.03),
                a3 = new BankAccount("Thomas", 0.02);

    a1.Deposit(100.0M);
    a1.Withdraw(300.0M);
    Console.WriteLine("Account 1: {0}", a1.Balance());

    a2.Deposit(1000.0M); a2.AddInterests();

    a3.Deposit(3000.0M); 
    a3.Withdraw(1103.0M); 
    a3.AddInterests();

    Console.WriteLine(a1); 
    Console.WriteLine(a2); 
    Console.WriteLine(a3); 
  }

}

Program: Output from the client program.
Account 1: -200,0
Kurt's account holds -200,0 kroner
Bent's account holds 1030,000 kroner
Thomas's account holds 1934,940 kroner

Program: Methods in the - slightly extended - class BankAccount. A version of class BankAccount with transaction logging. This version of the class is the basis for the exercise below.
using System;

public enum AccountTransaction {Withdrawing, Depositing, Interests};

public class BankAccount {

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

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

   public BankAccount(string owner, double interestRate) {
     this.interestRate = interestRate;
     this.owner = owner; 
     this.balance = 0.0M;
   }   

   public decimal Balance () {
     return balance;
   }

   private void LogTransaction(AccountTransaction kind, DateTime dt, 
                               decimal amount){
     // It is an exercise to implement this method
   }

   public void Withdraw (decimal amount) {
     this.LogTransaction(AccountTransaction.Withdrawing,
                         DateTime.Now, amount);
     balance -= amount;
   }

   public void Deposit (decimal amount) {
     this.LogTransaction(AccountTransaction.Depositing, 
                         DateTime.Now, amount);
     balance += amount;
   }

   public void AddInterests() {
     decimal interests = balance * (decimal)interestRate;
     this.LogTransaction(AccountTransaction.Interests, 
                         DateTime.Now, interests);
     balance += interests;
   }    

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

Exercise 3.7. The method LogTransaction in class BankAccount

In the accompanying BankAccount class we have sketched and used a private method named LogTransaction. Implement this private method and test it with the BankAccount client class.

Exercise 3.7. Course and Project classes

In this exercise you are asked to program three simple classes which keep track of the grading of a sample student. The classes are called BooleanCourse, GradedCourse, and Project.

A BooleanCourse encapsulates a course name and a registration of passed/not passed for our sample student.

A GradedCourse encapsulates a course name and the grade of the student. For grading we use the Danish 7-step, numerical grades 12, 10, 7, 4, 2, 0 and -3. You are also welcome use the enumeration type ECTSGrade from an earlier exercise. The grade 2 is the lowest passing grade.

In both BooleanCourse and GradedCourse you should write a method called Passed. The method is supposed to return whether our sample student passes the course.

The class Project aggregates two boolean courses and two graded courses. You can assume that a project is passed if at least three out of the four courses are passed. Write a method Passed in class Project which implements this passing policy.

Make a project with four courses, and try out your solution.

In this exercise you are supposed to make a simple and rather primitive solution. We will come back to this exercise when we have learned about inheritance and collection classes.

Read more about instance methods in the text book version of this material.

Class Variables
Slide Annotated slide Contents Index
References Textbook 
Class variables are less important than instance variables when we do object-oriented programming. Class variables are similar to global variables if they belong to a public class.

The concept class variable: A class variable belongs to the class, and it is shared among all instances of the class.

  • Class variables

    • are declared by use of the static modifier in C#

    • may be used as global variables - associated with a given class

    • do typically hold meta information about the class, such as the number of instances

Program: The class BankAccount with a class variable. The class BankAccount with a class variable nextAccountNumber, which can be used as a unique account number when a BankAccount class is instantiated.
using System;

public class BankAccount {

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

   private static long nextAccountNumber = 0;

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

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

   public decimal Balance () {
      return balance;
   }

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

Program: A client of class BankAccount.
using System;

public class BankAccountClient {

  public static void Main(){
    BankAccount a1 = new BankAccount("Kurt", 0.02),
                a2 = new BankAccount("Bent", 0.03),
                a3 = new BankAccount("Thomas", 0.02);

    a1.Deposit(100.0M);
    a2.Deposit(1000.0M); a2.AddInterests();
    a3.Deposit(3000.0M); a3.AddInterests();

    Console.WriteLine(a1); 
    Console.WriteLine(a2); 
    Console.WriteLine(a3); 
  }

}

Program: Output of the BankAccount client program.
Kurt's account, no. 1 holds 100 kroner
Bent's account, no. 2 holds 1030 kroner
Thomas's account, no. 3 holds 3060 kroner

Exercise 3.8. Sharing the Random Generator

In the Die class shown in the start of this lecture, each Die object creates its own Random object.

We observed that tosses of two or more instances of class Die will be identical. Explain the reason of this behavior.

Modify the Die class such that all of them share a single Random object. Consider different ways to implement this sharing. Rerun the Die program and find out if "the parallel tossing pattern" observed above has been alleviated.

Read more about class variables in the text book version of this material.

Class Methods
Slide Annotated slide Contents Index
References Textbook 
A class method is activated on a class. In contrast, an instance method is activated on an object. The Main method, which usually initiates our programs, is an example of a class method.

The concept class method: A class method is associated with the class itself, as opposed to an object of the class

  • A class method M in a class C

    • is declared by use of the static modifier in C#

    • can only access static members of the class

    • must be activated on the class as such

    • is activated as C.M(...) from outside C

    • can also be activated as M(...) from inside C

Program: A BankAccount class with static methods.
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) {
      nextAccountNumber++;
      accounts.Add(this);                                    
      this.accountNumber = nextAccountNumber;
      this.interestRate = 0.0;
      this.owner = owner;                                    
      this.balance = 0.0M;
   }

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

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

Program: A client BankAccount.
using System;

public class BankAccountClient {

  public static void Main(){
    BankAccount a1 = new BankAccount("Kurt", 0.02),
                a2 = new BankAccount("Bent", 0.03),
                a3 = new BankAccount("Thomas", 0.02);

    a1.Deposit(100.0M);
    a2.Deposit(1000.0M); a2.AddInterests();
    a3.Deposit(3000.0M); a3.AddInterests();

    BankAccount a = BankAccount.GetAccount(2);
    if (a != null) 
      Console.WriteLine(a); 
    else
      Console.WriteLine("Cannot find account 2"); 
  }

}

Program: Output from the BankAccount client program.
Bent's account, no. 2 holds 1030,000 kroner

Program: A typical problem: A class method that accesses instance variables.
using System;

public class BankAccountClient {

  BankAccount 
    a1 = new BankAccount("Kurt", 0.02),    // Error:
    a2 = new BankAccount("Bent", 0.03),    // An object reference is 
    a3 = new BankAccount("Thomas", 0.02);  // required for the
                                           // nonstatic field
  public static void Main(){

    a1.deposit(100.0);                      
    a2.deposit(1000.0); a2.addInterests();  
    a3.deposit(3000.0); a3.addInterests();  
                                            
    Console.WriteLine(a1); 
    Console.WriteLine(a2); 
    Console.WriteLine(a3); 
  }

}

Read more about class methods in the text book version of this material.

Static Classes and Partial Classes in C#
Slide Annotated slide Contents Index
References Textbook 
If you have a class with only class variables and class methods (only static members), you may chose to mark the class as static. A partial class will be aggregated by contributions from several source files.

A static class C can only have static members

A partial class is defined in two or more source files

  • Static class

    • Serves as a module rather than a class

    • Prevents instantiation, subclassing, instance members, and use as a type.

    • Examples: System.Math, System.IO.File, and System.IO.Directory

  • Partial class

    • Usage: To combine manually authored and automatically generated class parts.

Reference

Read more about static classes and partial classes in the text book version of this material.

Constant and readonly variables
Slide Annotated slide Contents Index
References Textbook 
C# supports constants - marked with const - which are computed and bound at compile time, before the program starts its execution. C# also supports variables - marked with readonly - which can only be assigned when the surrounding object is created. Once the object has been created, these variables are constant (read only) as well.

Constants and readonly variables cannot be changed during program execution

  • Constants declared with use of the const keyword

    • Computed at compile-time

    • Must be initialized by an initializer

    • The initializer is evaluated at compile time

    • No memory is allocated to constants

    • Must be of a simple type, a string, or a reference type

  • Readonly variables declared with use of the readonly modifier

    • Computed at object-creation time

    • Must either be initialized by an initializer or in a constructor

    • Cannot be modified in other parts of the program

Program: Legal use of constants and readonly variables.
using System;

class ConstDemo {
  const double    ca = 5.0,      
                  cb = ca + 1;   

  private readonly double roa = 7.0,     
                          rob = Math.Log(Math.E);  

  private readonly BankAccount
                  roba = new BankAccount("Anders");

  public ConstDemo(){   // CONSTRUCTOR
    roa = 8.0;   
    roba = new BankAccount("Tim");  
  }                                 

  public static void Main(){
    ConstDemo self = new ConstDemo();  
    self.Go(); 
  }

  public void Go(){
    roba.Deposit(100.0M);  
  }
}

Program: Illegal use of constant and readonly variables.
using System;

class ConstDemo {
  const double    ca = 5.0;

  private readonly double roa = 7.0;

  private readonly BankAccount
                  roba = new BankAccount("Anders");

  public ConstDemo(){   // CONSTRUCTOR
    ca = 6.0;  
  }

  public static void Main(){
    ConstDemo self = new ConstDemo();  
    self.Go(); 
  }

  public void Go(){
    ca = 6.0;   
    roa = 8.0;  
    roba = new BankAccount("Peter");  
  }
}

Read more about constants in the text book version of this material.

Objects and Classes
Slide Annotated slide Contents Index
References Textbook 
You are supposed to know the difference between a class and an object. This difference is explained on this page.

Classes are written and described in source programs

Objects are created and exist while programs are running

  • Objects are characterized by

    • Identity

    • State

    • Behavior

All objects cease to exist when the program execution terminates.

This is in conflict with the behavior of corresponding real-life phenomena, and it causes a lot of problems and challenges in many programs

Read more about classes vs. objects in the text book version of this material.

The current object - this
Slide Annotated slide Contents Index
References Textbook 
The latest receiver of a message is called the current object. This is the object on which the latest instance method has been called.

The current object in a C# program execution is denoted by the variable this

Program: The BankAccount - emphasizing this. This example shows two different uses of this. First, it may be necessary to get access to the current object as such. In the example it is necessary to add the current account to a list of accounts. The second use pertain to methods (and constructors). It is convenient to use the same name for both formal parameters of methods and instance variables of the surrounding class. To disambiguate these, this.x refers to the instance variable named x, and x refers to the local meaning of x.
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) {
      nextAccountNumber++;
      accounts.Add(this);                                    
      this.accountNumber = nextAccountNumber;
      this.interestRate = 0.0;
      this.owner = owner;                                    
      this.balance = 0.0M;
   }

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

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

Program: The BankAccount - A more radical use of this. In this example, any use of a member of the current object is prefixed with this. This is a more radical use of this, which highlights use of members in the current object in contrast to members in other objects.
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) {
      BankAccount.nextAccountNumber++;
      BankAccount.accounts.Add(this);
      this.accountNumber = nextAccountNumber;
      this.interestRate = 0.0;
      this.owner = owner; 
      this.balance = 0.0M;
   }

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

   public decimal Balance () {
      return this.balance;
   }

   public static long NumberOfAccounts (){
     return BankAccount.nextAccountNumber;
   }

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

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

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

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

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

  • this used for other related purposes in C#:

    • Reference to shadowed instance variables

    • Activation of another constructor

    • Definition of indexers

    • In definition of extension methods

References

Read more about the current object in the text book version of this material.

Visibility Issues
Slide Annotated slide Contents Index
References Textbook 
A type has a certain visibility in the namespace to which the type belongs. Similarly, a member of of type has a certain visibility. You can state the visibility explicitly by giving a so-called visibility modifier, or you can rely on defaults. It is recommended always to use explicit visibility modifiers, because it reflects that you have made a conscious choice.

Overview and clarification of some visibility issues

  • Types in namespaces

    • Either public or internal

    • Default visibility: internal

  • Members in classes

    • Either private, public, internal, protected or internal protected

    • Default visibility: private

  • Visibility inconsistencies

    • A type T can be less accessible than a method that returns a value of type T

Program: An illustration of the 'Inconsistent Accessibility' problem.
namespace N{

  class C {            
                       
  }                    

  public class D{

    public C M(){      // Compiler-time error message:
      return new C();  
                       // Inconsistent accessibility: 
                       // return type 'N.C' is less
                       // accessible than method 'N.D.M()'
    }

  }

}

Program: Solution to the problem.
namespace N{

  public class C {     // Now public visibility

  }

  public class D{

    public C M(){      // Now OK
      return new C();  
                       
    }

  }

}

Read more about visibility issues - in particular 'inconsistent accessibility' - in the text book version of this material.


Creating and Deleting Objects

Creating and Deleting Objects
Slide Annotated slide Contents Index
References Textbook 
On this page we outline possible ways to create and get rid of objects. Notice that all these possibilities are not necessarily supported in your favorite object-oriented programming language, such as C# or Java.

Possible approaches to creation and deletion of objects

  • Creating Objects

    • By instantiating classes

      • Implicitly: via variable declarations

      • Explicitly: on demand, by command

    • By copying existing object - cloning

  • Deleting Objects

    • Explicitly: on demand, by command

    • Implicitly: deleted when not used any longer - via use of garbage collection

Modern object-oriented languages support explicit object creation and implicit object deletion (by means of garbage collection)

Read more about creating and deleting objects in the text book version of this material.

Instantiation of classes
Slide Annotated slide Contents Index
References Textbook 
We now focus on a particular way of object creation, namely instantiation of classes. There are - in principle - two approaches that can be used. Again, it should be emphasized that both approaches are not necessarily supported in a given programming language.

The concept instantiation: Instantiation is the process of allocating memory to a new object of some class

  • Static instantiation:

    • The object is automatically created (and destroyed) when the surrounding object or block is created.

  • Dynamic instantiation:

    • The object is created on demand, by calling a particular operator (new).

On the next page we will see that C# relies on dynamic instantiation.

Instantiation of classes in C#
Slide Annotated slide Contents Index
References Textbook 
C# uses the new operation for dynamic instantiation of classes. Static instantiation cannot be used for classes in C#. As we will see later, static instantiation is used for stucts.

Classes must be instantiated dynamically with use of the new operator

The new operator returns a reference to the new object

Program: The class Point.
using System;

public class Point {
  private double x, y;

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

  public double Getx (){
    return x;
  }

  public double Gety (){
    return y;
  }

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

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

Program: Use of the class Point in a client class called Application.
using System;

public class Application{

  private static Point p0;   // Initialized to null

  public static void Main(){
    Point p1,                // NOT initialized
          p2 = new Point(1.1, 2.2);                  
                                                     
    p0 = p1 = p2;                                    
    p2.Move(3.3, 0);
    Console.WriteLine("{0} {1} {2}", p0, p1, p2);
  }

}

Program: Output from the Point client program.
Point: (4,4, 2,2).  Point: (4,4, 2,2).  Point: (4,4, 2,2).

Read more about instantiation of classes in the text book version of this material.

Initialization of objects
Slide Annotated slide Contents Index
References Textbook 
The step after instantiation is called initialization. Without initialization, the memory of the allocated object may contain random values. In this context, random values is the same as garbage! In order to avoid this, a new object should always be initialized. In C#, constructors are responsible for initialization of the instance variables.

The concept initialization: Initialization is the process of ascribing initial values to the instance variables of an object

  • Initialization of objects of type T

    • Via use of default values of T

      • zero for numeric types, false for bool, '\x0000' for char, and null for reference types

    • Via use of an initializer

    • Via special methods called constructors

Do not rely on default values!

It is recommended that you bundle all initializations of instance variables in constructors.

It is very important that a newly born object is initialized to a healthy citizen in the population of objects

Explicit initialization is always preferred over implicit initialization

Always initialize instance variables in constructors

Read more about initialization in the text book version of this material.

Constructors in C#
Slide Annotated slide Contents Index
References Textbook 
You should strive to organize initialization of all instance variables in constructors.

The concept constructor: A constructor is a special method which is called automatically in order to initialize a new instance of a class

  • Constructors in C#

    • Have the same name as the surrounding class

    • Do not specify any return value type

    • Are often overloaded - several different constructors can appear in a class

    • May - in a special way - delegate the initialization job to another constructor

    • In case no constructors are defined, there is a parameterless default constructor

      • As its only action, it calls the parameterless constructor in the superclass

    • In case a constructor is defined there will be no parameterless default constructor

Program: Constructors in class BankAccount. The naive implementation of three constructors.
using System;

public class BankAccount {

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

   public BankAccount(string owner) {
      this.interestRate = 0.0;
      this.owner = owner; 
      this.balance = 0.0M;
   }

   public BankAccount(string owner, double interestRate) {
      this.interestRate = interestRate;
      this.owner = owner; 
      this.balance = 0.0M;
   }

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

   public decimal Balance () {
      return balance;
   }

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

Program: Improved constructors in class BankAccount. A much better implementation of the three constructors.
using System;

public class BankAccount {

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

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

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

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

   public decimal Balance () {
      return balance;
   }

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

Program: Constructors in the class Die. Two constructors in class Die. One constructor uses the other (general) constructor.
using System;

public class Die {
  private int numberOfEyes;
  private Random randomNumberSupplier; 
  private readonly int maxNumberOfEyes;

  public Die (): this(6){}  

  public Die (int maxNumberOfEyes){
    randomNumberSupplier = 
      new Random(unchecked((int)DateTime.Now.Ticks));
    this.maxNumberOfEyes = maxNumberOfEyes;
    numberOfEyes = NewTossHowManyEyes();
  }   
    
  public void Toss (){
    numberOfEyes = NewTossHowManyEyes();
  }

  private int NewTossHowManyEyes (){
    return randomNumberSupplier.Next(1,maxNumberOfEyes + 1);
  }

  public int NumberOfEyes() {
    return numberOfEyes;
  }

  public override String ToString(){
    return String.Format("Die[{0}]: {1}", maxNumberOfEyes, numberOfEyes);
  }
}

Read more about constructors in C# in the text book version of this material.

Copy constructors
Slide Annotated slide Contents Index
References Textbook 
The easy way to program object copying is via copy constructors. A copy constructor takes a single parameter of the same type as the surrounding class.

It is sometimes useful to have a constructor that creates an identical copy of an existing object

Program: The class Die with a copy constructor.
using System;

public class Die {
  private int numberOfEyes;
  private Random randomNumberSupplier; 
  private readonly int maxNumberOfEyes;

  public Die (Die d){
    numberOfEyes = d.numberOfEyes;
    randomNumberSupplier = d.randomNumberSupplier;
    maxNumberOfEyes = d.maxNumberOfEyes;
  }   

  public Die (): this(6){}

  public Die (int maxNumberOfEyes){
    randomNumberSupplier = new Random(unchecked((int)DateTime.Now.Ticks));
    this.maxNumberOfEyes = maxNumberOfEyes;
    numberOfEyes = randomNumberSupplier.Next(1,maxNumberOfEyes + 1);
  }   
    
  public void Toss (){
    numberOfEyes = randomNumberSupplier.Next(1,maxNumberOfEyes + 1);
  }

  public int NumberOfEyes() {
    return numberOfEyes;
  }

  public override String ToString(){
    return String.Format("Die[{0}]: {1}", maxNumberOfEyes, numberOfEyes);
  }
}

Reference

The use of copy constructors is particularly useful when we deal with mutable objects

Read more about Copy constructors in the text book version of this material.

Initialization of class variables
Slide Annotated slide Contents Index
References Textbook 
Constructors are deserved for initialization of instance variables. Class variable should be initialized before initialization of any instance variable.

It is too late - and not natural - to initialize class variables in ordinary constructors

  • Initialization of a class variable of type T (at class load time)

    • Via the default value of type T

    • Via the static field initializers

    • Via a static constructor

In most cases, we rely on initializers for initialization of class variables.

In some rare situations we need the power of static constructors. An example is shown below.

Program: The class PlayingCard with a static constructor.
using System;

public class Card{
  public enum CardSuite { Spade, Heart, Club, Diamond};
  public enum CardValue { Ace = 1, Two = 2, Three = 3, Four = 4, Five = 5, 
                          Six = 6, Seven = 7, Eight = 8, Nine = 9,
                          Ten = 10, Jack = 11, Queen = 12, King = 13,
                        };

  private CardSuite suite;
  private CardValue value;

  public static Card[] allSpades = new Card[14];
  public static Card[] allHearts = new Card[14];
  public static Card[] allClubs = new Card[14];
  public static Card[] allDiamonds = new Card[14];

  static Card(){
    foreach(CardValue cv in Enum.GetValues(typeof(CardValue))){
      allSpades[(int)cv] = new Card(CardSuite.Spade, cv);
      allHearts[(int)cv] = new Card(CardSuite.Heart, cv);
      allClubs[(int)cv] = new Card(CardSuite.Club, cv);
      allDiamonds[(int)cv] = new Card(CardSuite.Diamond, cv);
    }
  }   

  public Card(CardSuite suite, CardValue value){
    this.suite = suite;
    this.value = value;
  }

  public CardSuite Suite{
    get { return this.suite; }
  }

  public CardValue Value{
    get { return this.value; }
  }

  public override String ToString(){
    return String.Format("Suite:{0}, Value:{1}", suite, value);
  }
}

Program: A client of class PlayingCard.
using System;

class Client{

  public static void Main(){
    foreach (Card c in Card.allSpades)
      Console.WriteLine(c);
  }

}

Program: Output from the PlayingCard client program.
Suite:Spade, Value:Ace
Suite:Spade, Value:Two
Suite:Spade, Value:Three
Suite:Spade, Value:Four
Suite:Spade, Value:Five
Suite:Spade, Value:Six
Suite:Spade, Value:Seven
Suite:Spade, Value:Eight
Suite:Spade, Value:Nine
Suite:Spade, Value:Ten
Suite:Spade, Value:Jack
Suite:Spade, Value:Queen
Suite:Spade, Value:King

Read more about initialization of class variables in the text book version of this material.


Collected references
Contents Index
The class System.Random MSDN2 API Documentation
Visibility of types in namespaces Later in these notes
Class Die
The static class System.Math MSDN2 API Documentation
Message passing and the current object Earlier in these notes
This in constructors Later in these notes
This in indexers Later in these notes
Example use of copy constructors Later in these notes

 

Chapter 3: Classes and Objects
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:13:12