Chapter 8: A Closer Look at Methods and Classes


This chapter resumes the examination of classes and methods. It begins by explaining how to control access to the members of a class. It then discusses the passing and returning of objects, method overloading, the various forms of Main( ), recursion, and the use of the keyword static.

Controlling Access to Class Members

In its support for encapsulation, the class provides two major benefits. First, it links data with code. You have been taking advantage of this aspect of the class since Chapter 6. Second, it provides the means by which access to members can be controlled. It is this second feature that is examined here.

Although C#’s approach is a bit more sophisticated, in essence, there are two basic types of class members: public and private. A public member can be freely accessed by code defined outside of its class. This is the type of class member that we have been using up to this point. A private member can be accessed only by methods defined by its class. It is through the use of private members that access is controlled.

Restricting access to a class’ members is a fundamental part of object-oriented programming because it helps prevent the misuse of an object. By allowing access to private data only through a well-defined set of methods, you can prevent improper values from being assigned to that data—by performing a range-check, for example. It is not possible for code outside the class to set the value of a private member directly. You can also control precisely how and when the data within an object is used. Thus, when correctly implemented, a class creates a “black box” that can be used, but the inner workings of which are not open to tampering.

C#’s Access Modifiers

Member access control is achieved through the use of four access modifiers: public, private, protected, and internal. In this chapter we will be concerned with public and private. The protected modifier applies only when inheritance is involved and is described in Chapter 11. The internal modifier applies mostly to the use of an assembly, which for C# loosely means a deployable program or library. The internal modifier is examined in Chapter 16.

When a member of a class is modified by the public specifier, that member can be accessed by any other code in your program. This includes methods defined inside other classes.

When a member of a class is specified as private, that member can be accessed only by other members of its class. Thus, methods in one class are not able to access a private member of another class. As explained in Chapter 6, if no access specifier is used, a class member is private to its class by default. Thus, the private specifier is optional when creating private class members.

An access specifier precedes the rest of a member’s type specification. That is, it must begin a member’s declaration statement. Here are some examples:

 public string errMsg; private double bal; private bool isError(byte status) { // ...

To understand the difference between public and private, consider the following program:

 // Public vs private access. using System; class MyClass {   private int alpha; // private access explicitly specified   int beta;          // private access by default   public int gamma;  // public access   /* Methods to access alpha and beta.  It is OK for a      member of a class to access a private member      of the same class.   */   public void setAlpha(int a) {     alpha = a;   }   public int getAlpha() {     return alpha;   }   public void setBeta(int a) {     beta = a;   }   public int getBeta() {     return beta;   } } class AccessDemo {   public static void Main() {     MyClass ob = new MyClass();     /* Access to alpha and beta is allowed only        through methods. */     ob.setAlpha(-99);     ob.setBeta(19);     Console.WriteLine("ob.alpha is " + ob.getAlpha());     Console.WriteLine("ob.beta is " + ob.getBeta());     // You cannot access alpha or beta like this: //  ob.alpha = 10; // Wrong! alpha is private! //  ob.beta = 9;   // Wrong! beta is private!     // It is OK to directly access gamma because it is public.     ob.gamma = 99;    } }

As you can see, inside the MyClass class, alpha is specified as private, beta is private by default, and gamma is specified as public. Because alpha and beta are private, they cannot be accessed by code outside of their class. Therefore, inside the AccessDemo class, neither can be used directly. Each must be accessed through public methods, such as setAlpha( ) and getAlpha( ). For example, if you were to remove the comment symbol from the beginning of the following line:

 //  ob.alpha = 10; // Wrong! alpha is private!

you would not be able to compile this program because of the access violation. Although access to alpha by code outside of MyClass is not allowed, methods defined within MyClass can freely access it, as the setAlpha( ) and getAlpha( ) methods show. The same is true for beta.

The key point is this: A private member can be used freely by other members of its class, but it cannot be accessed by code outside its class.

Applying Public and Private Access

The proper use of public and private access is a key component of successful object-oriented programming. Although there are no hard and fast rules, here are some general principles that serve as guidelines:

  1. Members of a class that are used only within the class itself should be private.

  2. Instance data that must be within a specific range should be private, with access provided through public methods that can perform range checks.

  3. If changing a member can cause an effect that extends beyond the member itself (that is, affects other aspects of the object), that member should be private, and access to it should be controlled.

  4. Members that can cause harm to an object when improperly used should be private. Access to these members should be through public methods that prevent improper usage.

  5. Methods that get and set the values of private data must be public.

  6. Public instance variables are permissible when there is no reason for them to be private.

Of course, there are many nuances that the preceding rules do not address, and special cases cause one or more rules to be violated. But in general, if you follow these rules, you will be creating resilient objects that are not easily misused.

Controlling Access: A Case Study

To better understand the “how and why” behind access control, a case study is useful. One of the quintessential examples of object-oriented programming is a class that implements a stack. As you probably know, a stack is a data structure that implements a first-in, last-out list. Its name comes from the analogy of a stack of plates on a table. The first plate on the table is the last one to be used.

A stack is a classic example of object-oriented programming because it combines storage for information along with the methods that access that information. Thus, a stack is a data engine that enforces the first-in, last-out usage. Such a combination is an excellent choice for a class in which the members that provide storage for the stack are private, and public methods provide access. By encapsulating the underlying storage, it is not possible for code that uses the stack to access the elements out of order.

A stack defines two basic operations:M push and pop. A push puts a value onto the top of the stack. A pop removes a value from the top of the stack. Thus, a pop is consumptive; once a value has been popped off the stack, it has been removed and cannot be accessed again.

The example shown here creates a class called Stack that implements a stack. The underlying storage for the stack is provided by a private array. The push and pop operations are available through the public methods of the Stack class. Thus, the public methods enforce the first-in, last-out mechanism. As shown here, the Stack class stores characters, but the same mechanism could be used to store any type of data:

 // A stack class for characters. using System; class Stack {   // these members are private   char[] stck; // holds the stack   int tos;     // index of the top of the stack   // Construct an empty Stack given its size.   public Stack(int size) {     stck = new char[size]; // allocate memory for stack     tos = 0;   }   // Push characters onto the stack.   public void push(char ch) {     if(tos==stck.Length) {       Console.WriteLine(" -- Stack is full.");       return;     }     stck[tos] = ch;     tos++;   }   // Pop a character from the stack.   public char pop() {     if(tos==0) {       Console.WriteLine(" -- Stack is empty.");       return (char) 0;     }     tos--;     return stck[tos];   }   // Return true if the stack is full.   public bool full() {     return tos==stck.Length;   }   // Return true if the stack is empty.   public bool empty() {     return tos==0;   }   // Return total capacity of the stack.   public int capacity() {     return stck.Length;   }   // Return number of objects currently on the stack.   public int getNum() {     return tos;   } }

Let’s examine this class closely. The Stack class begins by declaring these two instance variables:

 // these members are private char[] stck; // holds the stack int tos;     // index of the top of the stack

The stck array provides the underlying storage for the stack, which in this case holds characters. Notice that no array is allocated. The allocation of the actual array is handled by the Stack constructor. The tos member holds the index of the top of the stack.

Both the tos and stck members are private. This enforces the first-in, last-out stack mechanism. If public access to stck were allowed, then the elements on the stack could be accessed out of order. Also, since tos holds the index of the top element in the stack, manipulations of tos by code outside the Stack class must be prevented in order to avoid corruption of the stack. Access to stck and tos is available, indirectly, to the user of Stack through the various public methods described shortly.

The stack constructor is shown next:

 // Construct an empty Stack given its size. public Stack(int size) {   stck = new char[size]; // allocate memory for stack   tos = 0; }

The constructor is passed the desired size of the stack. It allocates the underlying array and sets tos to zero. Thus, a zero value in tos indicates that the stack is empty.

The public push( ) method puts an element onto the stack. It is shown here:

 // Push characters onto the stack. public void push(char ch) {   if(tos==stck.Length) {     Console.WriteLine(" -- Stack is full.");     return;   }   stck[tos] = ch;   tos++; }

The element to be pushed onto the stack is passed in ch. Before the element is added to the stack, a check is made to ensure that there is still room in the underlying array. This is done by making sure that tos does not exceed the length of stck. If there is still room, the element is stored in stck at the index specified by tos, and then tos is incremented. Thus, tos always contains the index of the next free element in stck.

To remove an element from the stack, call the public method pop( ). It is shown here:

 // Pop a character from the stack. public char pop() {   if(tos==0) {     Console.WriteLine(" -- Stack is empty.");     return (char) 0;   }   tos--;   return stck[tos]; }

Here, the value of tos is checked. If it is zero, the stack is empty. Otherwise, tos is decremented, and the element at that index is returned.

Although a push( ) and pop( ) are the only methods needed to implement a stack, some others are quite useful, and the Stack class defines four more. These are full( ), empty( ), capacity( ), and getNum( ), and they provide information about the state of the stack. They are shown here:

 // Return true if the stack is full. public bool full() {   return tos==stck.Length; } // Return true if the stack is empty. public bool empty() {   return tos==0; } // Return total capacity of the stack. public int capacity() {   return stck.Length; } // Return number of objects currently on the stack. public int getNum() {   return tos; }

The full( ) method returns true when the stack is full and false otherwise. The empty( ) method returns true when the stack is empty, and false otherwise. To obtain the total capacity of the stack (that is, the total number of elements it can hold), call capacity( ). To obtain the number of elements currently stored on the stack, call getNum( ). These methods are useful because the information they provide requires access to tos, which is private. They are also examples of how public methods can provide safe access to private members.

The following program demonstrates the stack:

 // Demonstrate the Stack class. using System; class StackDemo {   public static void Main() {     Stack stk1 = new Stack(10);     Stack stk2 = new Stack(10);     Stack stk3 = new Stack(10);     char ch;     int i;     // Put some characters into stk1.     Console.WriteLine("Push A through J onto stk1.");     for(i=0; !stk1.full(); i++)       stk1.push((char) ('A' + i));     if(stk1.full()) Console.WriteLine("stk1 is full.");     // Display the contents of stk1.     Console.Write("Contents of stk1: ");     while( !stk1.empty() ) {       ch = stk1.pop();       Console.Write(ch);     }     Console.WriteLine();     if(stk1.empty()) Console.WriteLine("stk1 is empty.\n");     // put more characters into stk1     Console.WriteLine("Again push A through J onto stk1.");     for(i=0; !stk1.full(); i++)       stk1.push((char) ('A' + i));     /* Now, pop from stk1 and push the element in stk2.        This causes stk2 to hold the elements in        reverse order. */     Console.WriteLine("Now, pop chars from stk1 and push " +                       "them onto stk2.");     while( !stk1.empty() ) {       ch = stk1.pop();       stk2.push(ch);     }     Console.Write("Contents of stk2: ");     while( !stk2.empty() ) {       ch = stk2.pop();       Console.Write(ch);     }     Console.WriteLine("\n");     // put 5 characters into stack     Console.WriteLine("Put 5 characters on stk3.");     for(i=0; i < 5; i++)       stk3.push((char) ('A' + i));     Console.WriteLine("Capacity of stk3: " + stk3.capacity());     Console.WriteLine("Number of objects in stk3: " +                       stk3.getNum());   } }

The output from the program is shown here:

 Push A through J onto stk1. stk1 is full. Contents of stk1: JIHGFEDCBA stk1 is empty. Again push A through J onto stk1. Now, pop chars from stk1 and push them onto stk2. Contents of stk2: ABCDEFGHIJ Put 5 characters on stk3. Capacity of stk3: 10 Number of objects in stk3: 5




C# 2.0(c) The Complete Reference
C# 2.0: The Complete Reference (Complete Reference Series)
ISBN: 0072262095
EAN: 2147483647
Year: 2006
Pages: 300

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