Theme index -- Keyboard shortcut: 'u'  Previous theme in this lecture -- Keyboard shortcut: 'p'  Next slide in this lecture -- Keyboard shortcut: 'n'Classes and Objects

A complete PDF version of the text book is now available. The PDF version is an almost complete subset of the HTML version (where only a few, long program listings have been removed). See here.

11.  Classes

The most important programming concept in object-oriented programming is the class. The programmer writes the classes in his or her source program. At run time, classes are used as blueprints/templates for instantiation of classes (creation of objects). In this chapter we will explore the concept of classes. This will be a relatively long journey through visibility issues, representation independence, instance and class variables, instance and class methods, and the notation of the current object. At the end of the chapter, in Section 11.14 we will discuss the important differences between classes and objects.

11.1 Classes11.9 Instance Methods
11.2 Perspectives on classes11.10 Class Variables
11.3 Visibility - the Iceberg Analogy11.11 Class Methods
11.4 Visible and Hidden aspects11.12 Static Classes and Partial Classes in C#
11.5 Program modification - the Fire Analogy11.13 Constant and readonly variables
11.6 Representation Independence11.14 Objects and Classes
11.7 Classes in C#11.15 The current object - this
11.8 Instance Variables11.16 Visibility Issues
 

11.1.  Classes
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

The single most important aspect of classes is encapsulation. As a matter of fact, I believe that the most important achievement of object-oriented programming is the idea of systematic encapsulation of variables and operations that belong together.

A class is a construct that surrounds a number of definitions, which belong together. Some of these definitions can be seen from the outside, whereas others are only relevant seen from the inside. Here follows a short 'definition' of a 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

The parts of a class which are visible from other classes forms the client interface of the class. In the figure, the interface of a class is drawn on the border of the box that surrounds the variables and operations. Thus, in the figure, only a subset of the operations - Op1, Op2, Op3, and Op4 - form the client interface of the class. All data parts are kept inside the class, and they cannot be directly used from other classes.

Figure 11.1    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 notion of interfaces between program parts - program building blocks - is important in general. In this section we talk about the interfaces between classes. It turns out that a class may have several different interfaces. The interface we care most about right now is called the client interface of a class C. There is another interface between C and classes that extends or specializes C. We have more to say about this interface in Section 27.2.


Exercise 3.2. 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#.

There is no solution to this exercise


 

11.2.  Perspectives on classes
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

In this section we discuss different ways to understand classes relative to already established understandings. You may safely skip this section if such discussion does not appeal to you.

Depending on background and preferences, different programmers may have different understandings of classes. Here follows some of these.

  • Different perspectives on classes:

    • An abstract datatype

    • A generalization of a record (struct)

    • A definition procedure

    • A module

Types and abstract datatypes are topics of general importance in computer science. But it is probably fair to state that the topic of types is of particular importance in the theoretical camp. Abstract datatypes have been studied extensively by mathematically inclined computer scientists, not least from an interest of specification. Boiled down to essence, a type can be seen as a set of values that possess a number of common properties. An abstract datatype is a set of such values and a set of operations on these values. The operations make the values useful. When we talk about abstract data types, the data details of the values in the type are put behind the scene.

In most imperative programming language, including Pascal and C, a record (a struct in C) is a data structure that groups data together. We often say that data parts are aggregated in a record. Records are called structs in C. It is a natural and nice idea to organize the operations of the grouped data together with the data themselves; In other words, to 'invite' the operations on records/structs into the record itself. In C#, structs are used as a "value variant" of a class. This is the topic in Section 14.1.

Abstractions can be formed on top of expressions. This leads to the functions. In the same way, procedures are abstractions of commands/statements. A call of a function is itself an expression, and a call of a procedure is a command/statement. From a theoretical point of view it is possible to abstract other syntactic categories as well, including a set of definitions. Such abstractions have been called definition procedures [Tennent81]. Classes can therefore be seen as definition procedures. Following the pattern from above, the activation of a definition procedure leads to definitions. It is not obvious, however, if multiple activations of a definition procedure is useful.

Finally, a module is an encapsulation, which does not act as a type. A module may, on the other hand, contain a type (typically a struct) that we treat as an abstract datatype. See Section 2.3 for our earlier discussion of modules.

 

11.3.  Visibility - the Iceberg Analogy
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

As stated in Section 11.1 visibility-control is an important aspect of classes. Inspired by Bertrand Meyers seminal book Object-oriented software construction, [Meyer88], we will compare a class with an iceberg.

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.

Figure 11.2    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.

Visibility-control is important because it protects the invisible parts of a class from being directly accessed from other classes. No other parts of the program can rely directly on details, which they cannot access. If some detail (typically a variable) of a class cannot be seen outside the class, it is much easier to modify this detail (e.g. replace the variable by a set of other variables) at a later point in time.

You may ask why we would like to modify details of our class. We can, of course, hope that we do not need to. But if the program is successful, and if it is alive many years ahead, it is most likely that we need to change it eventually. Typically, we will have to extend it somehow. It is also typical that we have to change the representation of some of our data. It is very costly if these changes cause a ripple effect that calls for many modifications throughout the whole program. It is very attractive if we can limit the area of the program that needs attention due to the modification. A programmer who use a programming language that guaranties a given visibility control policy is in a good position to deal with the consequences of the mentioned program modifications.

 

11.4.  Visible and Hidden aspects
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

Let us now be more concrete about class visibility. In this section we will describe which aspect to keep as class secrets, and which aspect to spread 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

The visible aspects should be kept at minimum level. The class name must be visible. The major interface of the class is formed by the signatures of selected operations. A signature of a method is the name of the method together with the types of the method parameters, and the type of the value returned by the method.

It is always recommended to keep the representation of data secret. It is almost always wrong to export knowledge about the instance variables of a class. Clients of the class should not care about - and should not know - data details. If we reveal data details it is very hard to change the data presentation at a later point in time. Let us stress again that it is a very typical modification of a program to alter the representation of data.

The bodies of the operations (the operation details beyond the operation signature) are hidden because operations are themselves abstractions (of either expressions or command). Finally, some operations serve as helper operations in order to encourage internal reuse within the class, and in order to prevent the operations of the class to become too large. Such helper operations should also be invisible to the clients of the class.

In Program 11.1 we show and emphasize the visible parts of the Die class from Program 10.1. We have dimmed the aspects of the Die class which are invisible to client classes (the aspects 'below the surface' relative to Figure 11.2).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 11.1    The class Die - aspects visible to clients emphasized.

Some programming language enforce that all instance variables of a class are hidden. Smalltalk [Goldberg83] is one such language. C# is not in this category, but we will typically strive for such discipline in the way we program in C#.

 

11.5.  Program modification - the Fire Analogy
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

In continuation of the iceberg analogy, which illustrated visibility issues, we will here illustrate program modification issues by an analogy to the spread of fire.

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

In large buildings, firewalls prevent a fire to destroy more than a single part of a building. Similarly, fire roads in forest areas are intended to keep fires to localized regions of the forest.

 

11.6.  Representation Independence
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

Let us now coin an important OOP programming principle: The principle of representation independence.

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

In essence, this is the idea we have already discussed in Section 11.4 and Section 11.5. Now we have name for it!

Below, in Program 11.2 we will show a class that is vulnerable in relation to the principle of representation independence. The class is written in C#. The class Point in Program 11.2 reveals its data representation to clients. This is because x and y are public. In Program 11.2 x and y are parts of the client interface of class Point.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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 11.2    A Point class with public instance variables - NOT Recommended .

The class shown below in Program 11.3 is a client of Point. It prompts the user for three points that we will assume form the shape of a triangle. In line 24-31 we calculate the circumference of this triangle. In these calculations we use the x and y coordinates of points directly, and quite heavily!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 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 11.3    A Client of Point.

Now assume that the programmer of class Point changes his or her mind with respect to the representation of points. Instead of using rectangular x and y coordinates the programmer shifts to polar coordinates. This is a representation of points that uses an angle between 0 and 2 pi, and a radius. The motivation behind the shift of representation may easily be that some other programmers request a rotation operation of the class Point. It is easy to rotate a "polar point". This leads to a new version of class Point, as sketched in Program 11.4. We are, of course, interested in the survival of Program 11.3 and other similar program. Imagine if there exists thousands of similar code lines in other classes!.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 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(){
    ...
  }
}
Program 11.4    A version of class Point modified to use polar coordinates - NOT Recommended.

We will not solve the rest of the problem at this point in time. We leave the solution as challenge to you in Exercise 3.3. In the lecture, which I give based on these notes, I am likely discuss additional elements of a good solution in C#.

Encapsulated data should always be hidden and private within the class


Exercise 3.3. 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.

Solution


 

11.7.  Classes in C#
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

In this and the following sections we will study classes in C#, instance variables, instance methods, class variables (static variables), and class methods (static methods).

The syntactic composition of classes is as follows.


class-modifiers class class-name{
  variable-declarations
  constructor-declarations
  method-declarations
}
Syntax 11.1    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.

Notice, however, that the full story is somewhat more complicated. Inheritance is not taken into account, and only a few class members are listed. In addition, the order of the class members is not constrained as suggested by Syntax 11.1.

The default visibility of members in a class is private. It means that if you do not provide a visibility modifier of a variable or a method, the variable or method will be private. This is unfortunate, because a missing visibility modifier typically signals that the programmer forgot to decide the visibility of the member. It would have been better design of C# to get a compilation error or - at least - a warning.

The following gives an overview of different kinds of members - variables and methods - in a class:

  • 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

In the following four sections - from Section 11.8 to Section 11.11 - we will study instance variables, instance methods, class variables, and class methods in additional details. This is long journey! You will be back on track in Section 11.12.

 

11.8.  Instance Variables
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

All objects of a particular class have the same set of variables. Each object allocates enough memory space to hold its own set of variables. Thus, the values of these variables may vary from one instance (object) to another. Therefore the variables are known as instance variables.

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.

Unfortunately, the terminology varies a lot. Instance variables are officially known as fields in C#. Instance variables are, together with constants, known as data members. The term member is often used for all declarations contained in a class; This covers data members and function members (constructors, methods, properties, indexers, overloaded operators, and others). Some object-oriented programming languages (Eiffel, for instance) talk about attributes instead of instance variables. (In C#, attributes refer to an entirely different concept, see Section 39.6).

Below, in Program 11.5, we show an outline of a BankAccount class programmed in C#. The methods are not shown in this version of the class. The class has three instance variables, namely interestRate (of type double), owner (of type string), and balance (of type decimal, a type often used to hold monetary data). In addition the class has three constructors and a number methods, which are not shown here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;
   }   


   // Remaining methods are not shown here 
}
Program 11.5    Instance variables in a sketch of the class BankAccount.

In the BankAccountClient class in Program 11.6 we create three different BankAccount objects. The variables a1, a2, and a3 hold references to these objects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 11.6    Creation of three bank accounts.

Following the calls of the Deposit and AddInterests operations the three objects can be depicted as shown in Figure 11.4. The output of the program is shown in Figure 11.4. Listing 11.7 (only on web).

1
2
3
Kurt's account holds 100 kroner
Bent's account holds 1030 kroner
Thomas's account holds 3060 kroner
Listing 11.7    Output from the BankAccount client program.

Figure 11.4    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.4. 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?

Solution


 

11.9.  Instance Methods
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

Instance methods are intended to work on (do computations on) the instance variables of an object in a class. An instance method M must always be activated on an instance (an object) of the class to which M belongs.

Activating or calling an instance method is often thought of as message passing (see Section 2.1). The object, on which the method is activated, is called the receiver of the message. The callee (the object from which the message is sent) is - quite naturally - called the sender.

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

Notice that an instance method can access all instance variables of a class, including the private ones. An instance method can also access class variables (see Section 11.10).

The form object.M(...) must be used if a method M is activated on an object different from the current object. The short form M(...) can be used in case M is activated on the current object. It is, however, often more clear to write this.M(...) With this notation we are explicit about the receiver of the message; Also, with the notation this.M(...), we use dot notation consistently whenever we activate an instance method. The choice between M(...) and this.M(...) depends on the chosen coding style. For more details on this see Section 11.15.

Conceptually you may imagine that each individual object has its own instance methods, in the same way as we in Section 11.8 argued that each individual object has its own instance variables. In reality, however, all instances of a given class can share the instance methods.

Program 11.8 shows a version of the BankAccount class in which the instance methods are highlighted. The method LogTransaction relies on the enumeration type AccountTransaction defined just before the class itself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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";
   }
}
Program 11.8    Methods in the - slightly extended - class BankAccount.

Most of the instance methods in Program 11.8 are public. The method LogTransaction is private, however. Therefore, LogTransaction cannot be called from a client of BankAccount. The method LogTransaction is called from the public methods Withdraw, Deposit, and AddInterests. Notice the way one instance method calls another instance method on the same object. As discussed above, we could drop ' this. '. It is very common to do so.

In Program 11.9 (only on web) we show a client of class BankAccount. It is quite similar to Program 11.6. Notice again the activation of the instance methods Deposit and AddInterests on the bank accounts referred to by a1, a2, and a3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 11.9    Selected messages to BankAccount methods from a client class.


Exercise 3.5. 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.

Solution


Exercise 3.6. 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.

Solution


 

11.10.  Class Variables
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

A class variable in a class C is shared between all instances (objects) of C. In addition, a class can be used even in the case where there does not exist any instance of C at all. Some classes are not intended to be instantiated. Such classes act as modules, cf. our discussion of modules in Section 2.3.

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

In Program 11.10 we show a new version of the BankAccount class, in which there is a private, static variable nextAccountNumber of type long. When we make a BankAccount object, we give it a unique account number. The output of the BankAccount client program in Program 11.11 (only on web) is shown in Listing 11.12. The program output reveals the effect of the static variable nextAccountNumber.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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;
   }   

   // Some methods not shown in this version

   public override string ToString() {
      return owner + "'s account, no. " + accountNumber + " holds " +
            + balance + " kroner";
   }
}
Program 11.10    The sketch of class BankAccount with a class variable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 11.11    A client of class BankAccount.

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


Exercise 3.7. 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.

Solution


 

11.11.  Class Methods
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

Class methods are not connected to any instance of a class. Thus, class methods can be activated without providing any instance of the class. A class method M in a class C is activated by C.M(...). The tree dots stand for possible actual parameters.

The static method Main plays a particular role in a C# program, because the program execution starts in Main. (Notice that Main starts with a capital M). It is crucial that Main is static, because there are objects around at the time Main is called. Thus, it is not possible to activate any instance method at that point in time! We have seen Main used many times already. There can be a Main method in more than one class. Main is either parameter less, or it may take an array of strings (of type String[]).

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

In order to illustrate the use of static methods in C# we extend Program 11.10 with a couple of static methods, see line 32-41 of Program 11.13. The static method GetAccount is the most interesting one. It searches the static accounts variable (of type ArrayList) for an account with a given number. It returns the located bank account if it is found. If not, it returns null. Notice the way the GetAccount method is used in Program 11.14.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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 static long NumberOfAccounts (){
     return nextAccountNumber;
   }

   public static BankAccount GetAccount (long accountNumber){
      foreach(BankAccount ba in accounts)
        if (ba.accountNumber == accountNumber)
           return ba;
      return null;
   }
 
   // Some BankAccount methods are not shown in this version

}
Program 11.13    A sketch of a BankAccount class with static methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 11.14    A client BankAccount.

When we run Program 11.14 we get the output shown in Listing 11.15 (only on web).

1
Bent's account, no. 2 holds 1030,000 kroner
Listing 11.15    Output from the BankAccount client program.

In Program 11.16 we show an example of a typical error. I bet that you will experience this error many times yourself. Can you see the problem? If not, read the text below Program 11.16.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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); 
  }

}
Program 11.16    A typical problem: A class method that accesses instance variables.

The variables a1, a2, and a3 in Program 11.16 are instance variable of class BankAccountClient. Thus, these variables are used to hold the state of objects of type BankAccountClient. The problem is that there does not exist any object of type BankAccountClient. We only have the class BankAccountClient. Therefore we need to declare a1, a2, and a3 as static. Alternatively, we can rearrange the program such that a1, a2, and a3 become local variables of the Main method. As yet another alternative, we can instantiate the class BankAccountClient, and move the body of Main to an instance method. The latter alternative is illustrated in Program 11.17.

 

11.12.  Static Classes and Partial Classes in C#
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

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.

It is possible to use the modifier 'static' on a class. A class marked as static can only have static members, and it cannot be instantiated. A static class is similar to a sealed class (see Section 30.4) in the sense that it cannot be subclassed. However, a static class is more restrictive, because it also disallows instance members, and it cannot be used as a type in field declarations and in method parameter lists.

There are some pre-existing C# classes that exclusively contain static methods. The class System.Math is such a class. It contains mathematical constants such as e and pi. It also contains commonly used mathematical functions such as Abs, Cos, Sin, Log, and Exp. It would be strange (and therefore illegal) to attempt an instantiation of such a class.

The static classes File and Directory in the namespace System.IO are discussed in Chapter 38.

A partial class, marked with the partial modifier, can be used if it is practical to aggregate a class from more than one source file. This is, in particular, handy when a class is built from automatically generated parts and manually authored parts (such as a GUI class). Use of partial classes may also turn out to be handy when a group of programmers participate in the programming of a single, large class.

 

11.13.  Constant and readonly variables
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

The variables we have seen until now can be assigned to new values at any time during the program execution. In this section we will study variable with no or limited assignment possibilities. Of obvious reasons, it is confusing to call these "variables". Therefore we use the term "constant" instead.

C# supports two different kinds of constants. Some constants, denoted with the const modifier, are bound at compile time. Others, denoted with the readonly modifier, are bound at object creation time.

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

It can be noticed that compile-time bound constants can only be of simple types, string, or a reference type. In addition, for non-string reference types, the only possible value is null.

Program 11.17 demonstrate some legal uses of constant and readonly variables. The elements emphasized with green are all legal and noteworthy. Notice first that we in Main instantiates the ConstDemo class, such that we can work on instance variables, as opposed to (static) class variables.

In line 4 we bind the constant ca to 5.0 and the constant cb to 6.0. This is done by the compiler, before the program starts executing. Notice that the compiler can carry out simple computations, as in line 5. In line 7 and 8 we bind the readonly variables roa and rob to 7.0 and to the value of the expression Log(e). It is possible to assign to roa and rob in the constructor, but after the execution of the constructor roa and rob are non-assignable. In line 11 we assign roba to a new BankAccount. Notice that it - in addition - is legal to assign to read-only variables in constructors (line 14 and 15). This is - on the other hand - the last possible, legal assignments to roa and roba. In line 24 we see that we can mutate a bank account despite that the account is referred by a readonly variable. We modify the object, not the variable that references the object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 11.17    Legal use of constants and readonly variables.

Program 11.18 domonstrates a number of illegal uses of constants and readonly variables. The elements emphasized with red are all illegal. The compiler catches all of them. In line 12 and 21 we attempt an assignment to the (compile-time) constant ca. This is illegal - even in a constructor. In line 22 and 23 we see that it is illegal to assign to readonly variables, such as roa and roba, once they have been initialized.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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");  
  }
}
Program 11.18    Illegal use of constant and readonly variables.

 

11.14.  Objects and Classes
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

At an overall level (as for instance in OOA and OOD) objects are often characterized in terms of identity, state, and behavior. Let us briefly address each of these, and relate them to programming concepts.

An object has an identity which makes it different and distinct from any other object. Two objects which are created by two activations of the new operator never share identity (they are not identical). In the practical world, the identity of an object is associated to its location in the memory: its address. Two objects are identical if their addresses are the same. But be careful here. The address of an object is not necessarily fixed and constant through the life time of the object. The object may be moved around in the memory of the computer, without losing its identify.

The state of the object corresponds to the data, as prescribed by the class to which the object belongs. As such, the state pertains to the instance variables of the class, see Section 11.8.

The behavior of the object is prescribed by the operations of the class, to which the object belongs. We have already discussed instance methods in Section 11.9. In Chapter 18 through Chapter 23 we will discuss operations, and hereby object behavior, in great details.

We practice object-oriented programming, but we write classes in our programs. This may be a little confusing. Shouldn't we rather talk about class-oriented programming?

When we write an object-oriented program, we are able to program all (forthcoming) objects of a given type/class together. This is done by writing the class. Thus, we write the classes in our source programs, but we often imagine a (forthcoming) situation where the class "is an object" which interacts with a number of other objects - of the same type or of different types.

At run time, the class that we wrote, prescribes the behavior of all the objects which are instances of the class.

In our source program we deal with classes. The classes exist for a long time - typically years. In the running program we have objects. The objects exist while the program is running. A typical program runs a few seconds, minutes, or perhaps hours. Often, we want to preserve our objects in between program executions. This turns out to be a challenge! We discuss how to preserve objects with use of serialization in Section 39.1.

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

There are no objects in the source programs! Only classes. You may ask if there are classes in the running program. It makes sense to represent the classes in the running program, such that we can access the classes as data. Most object-oriented systems today represent the classes as particular objects called metaobjects. This is connected to an area in computer science called reflection.

Classes are written and described in source programs

Objects are created and exist while programs are running

 

11.15.  The current object - this
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

We have earlier discussed the role of the current object, see Section 10.3.

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

this is used for several different purposes in C#:

  • Reference to shadowed instance variables

  • Activation of another constructor

  • Definition of indexers

  • In definition of extension methods

This use of this for access of shadowed instance variables has been used in many of the classes we have seen until now. For an example see line 10 of Program 11.2.

Use of this for activation of another constructor is, for instance, illustrated in line 10 and 14 of Program 12.4.

Use of this in relation to definition of indexers is discussed in Section 19.1, illustrated for instance in line 10 of Program 19.1.

 

11.16.  Visibility Issues
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

In this section we will clarify some issues that are related to visibility. We will, in particular, study a type of error which is difficult to deal with.

Let us first summarize some facts about visibility of types and members:

  • 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

Below we will rather carefully explain the mentioned inconsistency problem.

In Program 11.19 we have shown an internal class C in a namespace N. As given in Program 11.19 C is only supposed to be used inside the namespace N. In reality we have forgotten to state that C is public in N. I every now and then forget the modifier "public" in front of "class C" (line 3). I guess that you will run into this problem too - sooner og later.

Based on the internal class C in the namespace N we will now describe a scenario that leads to an error that can be difficult to understand. The class D is also located in N, and therefore D can use C. Class D is public in N. (If D had been located in another namespace, it would not have access to class C). A method M in class D makes and returns a C-object.

We cannot compile the program just described. We get an "inconsistent accessibility error". The compiler tells you that the return type of method M (which is C) is less accessible than the method M itself. In other words, M returns an object of a type, which cannot be accessed.

The cure is to make the class C public in its namespace. Thus, just add a public modifier in front of "class C" in line 3 of Program 11.19.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 11.19    An illustration of the 'Inconsistent Accessibility' problem.

Please notice this kind of compiler error, and the way to proceed when you get it. I have witnessed a prospective student programmer who used several days to figure out what the compiler meant with the "inconsistent accessibility error". Now you are warned!

 

11.17.  References
[Goldberg83]Adele Goldberg and David Robson, Smalltalk-80 The Language and its Implementation. Addison-Wesley Publishing Company, 1983.
[Meyer88]Bertrand Meyer, Object-oriented software construction. Prentice Hall, 1988.
[Tennent81]Tennent, R.D., Principles of Programming Languages. Prentice Hall, 1981.

Generated: Monday February 7, 2011, 12:13:51
Theme index -- Keyboard shortcut: 'u'  Previous theme in this lecture -- Keyboard shortcut: 'p'  Next slide in this lecture -- Keyboard shortcut: 'n'