Copyright © 2006 David Schmidt

Chapter 99:
C#


99.1 The Main function
    99.1.1 Locating and using the C# compiler
99.2 Arithmetic and conditionals
99.3 Strings and loops: while, for, foreach
99.4 C#'s arrays
99.5 Functions
99.6 In C#, a module is a class
    99.6.1 ``Static'' modules
99.7 Objects are constructed from classes
    99.7.1 Subclasses and method override


Python is a convenient and fun programming language, but its ease of use makes it easy for others with bad intentions to break its programs. So, Python is not always the first choice for building programs that will be sold to and used by the general public.

C# (``C sharp'') is a programming language developed by Microsoft for secure programming. C# is closely related to C++ and Java, and it makes many small improvements on these two languages. In addition, Microsoft has written a huge, useful library of C# program modules and classes called .NET --- by learning C# you will be able to use .NET. There is also a powerful IDE for C# called Visual Studio, which you will learn to use in a later course.

C# contains many protection mechanisms to hinder abuse and is a good choice for ``industrial strength'' computer programming. On the negative side, C#'s protection mechanisms cause the language to be verbose (there are lots more keywords and brackets than in Python) and clumsy (nice data structures like tuples, dictionaries are not available). But for practical reasons, it is important to learn how to adapt one's Python programming skills to C#.

There is an important conceptual difference between Python and C#: Python is a ``write and test'' programming language --- a scripting language, where a person types commands and tries them immediately. This immediate testing is done with the aid of the Python interpreter, which rests in computer storage with the Python program and mediates between the program and the computer's processor.

In contrast, C# is a ``check then execute'' language --- a batch language --- where the entire C# program must first be inspected and checked by the C# compiler program. If the compiler finds no grammatical errors in the C# program, then the compiler generates a translated version of the program as an .exe-file, which can then be executed by itself, working directly with the processor.

The check-then-execute style does well at spotting errors in advance of using the program. Also, since an interpreter program is not needed to use the translated (compiled) C# program, the latter can be embedded into computer chips in appliances like toasters, elevators, cars, and airplanes, and pacemakers.


99.1 The Main function

Every C# program has a Main function, which is called when a user starts the program. (Functions in C# are also called methods.)

A one-module Python program can be adapted without too much pain into a C# program. Figure 1 gives an example, which prints a greeting to a person after they type their name. First, here is the Python coding:

=================================

name = raw_input("Please type your name: ")
print "Hello there, " + name + "!"

======================================
And here is the C# coding:
FIGURE 1======================================

using System;
/// <remarks>Hello  demonstrates input and output
///   assumed input: a person's name
///   guaranteed output: a personalized greeting, sent to the command
///   window and a dialog </remarks>
public class Hello
{  public static void Main()
   {  Console.Write("Please type your name: ");
      string input = Console.ReadLine();
      Console.WriteLine("Hello there," + input + "!");
    }
}

ENDFIGURE============================================================
Here are points to remember:


99.1.1 Locating and using the C# compiler

Say that you saved the above C# program in the file, Hello.cs. First, you must compile the program (check spelling, grammar, data types, and translate into an exe-file). Do this by opening a command window, opening the folder that holds Hello.cs, and typing at the command line,

csc /debug+ Hello.cs
If you make a spelling or punctuation error, you will be informed; read the error message, note the line and character where the error appears, correct it, and compile again. If the program is properly spelled, then you will receive a prompt, and a file, Hello.exe, will be constructed in the same folder where Hello.cs lives. You type next
Hello
This executes the program directly, without assistance of an interpreter or helper modules --- the program operates all alone.

If your program contains an indexing or some other execution error, the program will halt. Because you included the /debug+ option when you compiled the program, the compiler has inserted instructions that will tell you the line where the execution error occurred. (If you had typed merely, csc Hello.cs, then an execution error causes the program to stop with no information about the source of the error.)

If you type csc /debug+ Hello.cs in the command window, and you see

'csc' is not recognized as an internal or external command,
operable program or batch file
then it means that you must help the command window locate the C# compiler, csc. Here's how:
  1. First, locate the C# compiler on your computer. (You can use your computer's search facility: press Start in the lower left corner, press Search, then within the Search window, select ``Search all files'' and then type a search for csc.exe.)

    You will probably locate the program in a folder named something like

    c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727
    
    This folder is the ``path'' to the compiler.
  2. Next, you must tell the command window the path to the compiler. The simplest way to do this is to open a new command window and type this command:
    set path=c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727
    
    Now, you can cd to the folder that contains your C# program and type csc /debug+ Hello.cs
  3. It is a nuisance to type the set path command each time you open a command window, and if you prefer to set the path permanently, then This permanently adds the path to the C# compiler to the paths used by the command window. To confirm you did this correctly, open a command window and type
    path
    
    You will see the list of folders (paths) that the command window searches when you ask it to execute a program (like notepad or csc). The last folder in the list should be the path to the C# compiler that you just added. You can also check by typing just
    csc
    
    and you will get a message from the C# compiler.

In addition, your computer might already have an IDE (Integrated Development Environment) for C# (e.g., Visual Studio). To find out if you do, open a file window and locate the icon for Hello.cs. Double-click on it. If a complex IDE window appears, and your program appears in the IDE's window, then you can compile and execute the program by clicking on the buttons or menu items of the IDE --- Look for a button named Compile or Build to compile the program. Then, look for a button named Execute.


99.2 Arithmetic and conditionals

C# programs can compute on numbers, just like Python. Here is a small program that reads an integer and computes its fractional reciprocal:

==============================================

using System;
///  Reciprocal  demonstrates arithmetic
///   assumed input: an integer 
///   guaranteed output: the number's reciprocal
public class Hello
{  public static void Main()
   {  Console.Write("Please type a nonzero int: ");

      string input = Console.ReadLine();  // we must read a string
      int n = Int32.Parse(input);  // convert string to an int
      double answer = 1.0 / n;     // double  means fractional num

      Console.WriteLine("1.0 / " + input + "  is  " + answer);
    }
}

====================================================
Here are some important points:

Here is a second arithmetic example: the MakeChange3.py program that appeared in Chapter 4 of these notes, coded in Python:

==============================

# MakeChange3
#  calculates change in coins for a dollars, cents amount

dollars = int(raw_input("Please type dollars amount: "))
cents = int(raw_input("Please type a cents amount: "))

if not ((dollars >= 0) and (cents >= 0) and (cents <= 99)) :
    print "Error"
else :
    money = (dollars * 100) + cents
    print "Quarters =", (money / 25 )
    money = money % 25
    print "Dimes =", (money / 10)
    money = money % 10
    print "Nickels =", (money / 5)
    money = money % 5
    print "Pennies =", money

============================================
Here is the C# coding, placed in a file named MakeChange.cs:
FIGURE 2===================================================

using System;
///<remarks>MakeChange
///   calculates change in coins for a dollars, cents amount.
///   assumed input: two numbers ---
///        a dollars amount, a nonnegative integer;
///        a cents amount, an integer between 0 and 99. 
///   guaranteed output: the coins for the amount  </remarks>
public class MakeChange
{  public static void Main()
   {  Console.Write("Please type dollars amount: ");
      string input = Console.ReadLine();
      int dollars = Int32.Parse(input);  // convert input into an int
      Console.Write("Please type cents amount: ");
      int cents = Int32.Parse(Console.ReadLine());
      if ( !((dollars >= 0) && (cents >= 0) && (cents <= 99)) )
           { Console.WriteLine("Error"); }
      else {
	     int money = (dollars * 100) + cents;
	     Console.WriteLine("Quarters = " +  (money / 25));
             money = money % 25;
             Console.WriteLine("Dimes = " + (money / 10));
             money = money % 10;
             Console.WriteLine("Nickels = " + (money / 5));
             money = money % 5;
             Console.WriteLine("Pennies = " + money);
           }
    }
}

ENDFIGURE=====================================================
This program uses a conditional command.

Say that you saved the above C# program into the file, MakeChange.cs. As before, you must compile the program (check spelling, grammar, and translate into an intermediate format). Do this by opening a command window, opening the folder that holds MakeChange.cs, and typing at the command line,

csc /debug+ MakeChange.cs
If you make a spelling or punctuation error, you will be informed; read the error message, correct the error, and try to compile again. If the program is properly spelled, then you will receive a prompt, and you can type next
MakeChange
This executes the program, which behaves the same as the Python version.

You can generate random numbers in C# just as easily as done in Python; do it like this:

Random r = new Random();  // starts a random-number generator and names it  r
  ...
int num = r.Next(6);  // assigns to  num  a random number in the range 0..5
You can use r.Next(...) over and over to generate random numbers.


99.3 Strings and loops: while, for, foreach

C#'s while-loop behaves exactly the same as Python's. Also, strings are treated similarly. Recall this Python program from Figure 1 of Chapter 4:
=====================================

# PrintChars 
#  prints a string's characters from left to right, one at a time:

text = raw_input("Type a string: ")

index = 0  # remembers which character we are looking at in  text
while  index < len(text) :
    # invariant: we have printed  text[0], text[1], ..., up to text[index-1]
    print  text[index]
    index = index + 1

raw_input("\n\npress Enter to finish")

ENDFIGURE=============================

Here is how it looks in C#:

FIGURE 3================================================

using System;
///<remarks> PrintChars0 
///    prints a string's characters from left to right, one at a time </remarks>
public class PrintChars0
{ public static void Main()
  { 
    Console.Write("Please type a string: ");
    string text = Console.ReadLine();
    int index = 0;  // remembers which character we are looking at in  text
    while ( index < text.Length ) {
        // invariant: we have printed  text[0], text[1], ..., to text[index-1]
	Console.Write(text[index]);
	index = index + 1;
    }
  }
}

ENDFIGURE=====================================================

The only novelty is that we use text.Length to obtain the integer length of string text.

Like its ancestor languages, C++ and Java, C# includes a counting form of for-loop. Here is the above program, reprogrammed with C#'s for-loop:

FIGURE 4===================================================================

using System;
public class PrintChars1
{ public static void Main()
  { Console.Write("Please type a string: ");
    string text = Console.ReadLine();
    
    for ( int index = 0;  index < text.Length;  index = index + 1 )
	{ char c = text[index];
          Console.Write(c);
        }
  }
}

=========================================================================
With the C# for-loop, you must declare a variable, i, that counts from 0 to the length of string text and extract its characters one at a time: for for ( int i = 0; i < text.length(); i = i + 1 ):

Also, note that a single letter is called a char in C#.

The C# for-loop proves useful when we must process the elements of a structure in a nonstandard order. For example, we print backwards the letters within string text this simply:

   for ( int i = text.Length;  i >= 0;  i = i-1 )
       { Console.Write(text[i]); }

Finally, the Python for-loop is called foreach in C#. Here is the above example, one more time:

FIGURE 5=====================================================
using System;
public class PrintChars2
{ public static void Main()
  { Console.Write("Please type a string: ");
    string text = Console.ReadLine();
    
    foreach (char letter in text)
      { Console.Write(letter); }
  }
}
======================================================
Notice that the auxiliary variable, letter, must be declared with its data type, char.

A standard example is alphabetizing (sorting) a string. Here is the program from Figure 6 of Chapter 4:

=====================================================

# Sort   alphabetizes a string.
# assumed input: s - a string
# guaranteed output: output - the characters in  s  arranged alphabetically

s = raw_input("Type word to sort: ")
output = ""

for letter in s :
    # invariant: at the i-th iteration,
    #   output  holds the first i-1 letters in  s,  alphabetized

    # Find the position in  output  where we should insert  letter:
    index = 0
    for alpha in output :
        if  letter < alpha :
	    break
        else :
	    index = index + 1
    # Insert  letter  at position  index  in  output:
    output = output[:index] + letter + output[index:]

print output

=====================================================

The program makes clever use of Python's for-loop and substring functions. We can repeat the strategy in C#:

FIGURE 6===============================================

using System;
/// <remarks>  Sort  alphabetizes a string.
///   assumed input: s - a string.
///   guaranteed output: output - the characters in  s,
///        arranged alphabetically.  </remarks>
public class Sort
{ public static void Main()
  {
    Console.Write("Type word to sort: ");
    string word = Console.ReadLine();
    String output = "";

    foreach (char letter  in  word)
      // invariant: at the i-th iteration,
      //   output  holds the first i-1 letters in  word,  alphabetized
      {
        int index = 0;
        // Find the position in  output  where we should insert  letter:
        foreach (char alpha  in  output)
          {
	    if ( letter < alpha )
		   { break; }
            else
		   { index = index + 1; }
          }
          // Insert  letter  at position  index  in  output:
          output =
             output.Substring(0,index) + letter + output.Substring(index);
        }
    Console.WriteLine(output);
  }
}

ENDFIGURE=====================================================

The example program shows that we can use break to exit a loop. Also, there are the two important methods for building substrings of a string:


99.4 C#'s arrays

One of the disappointments in C# is the absence of interesting data structures. There are no lists that grow, no tuples, and no dictionaries. There is only an array, whose size must be fixed when the array is used for its first time. A C# array is essentially a Python list that cannot grow in length.

(Note: Fortunately, the .NET library contains some C#-written classes that simulate lists and dictionaries. We won't study these here but focus instead on the C# core language. You will study the .NET versions in the next course.)

Before using an array, one must state the data type of elements the array holds and the length of the array. Repeating the starting example in Chapter 5, here is a 3-element array of strings that describes the contents of my clothes chest. The array is constructed and its address is assigned to the variable, chest:

string[] chest = new string[3];
chest[0] = "3 shirts";
chest[1] = "2 pants";
chest[2] = "no socks";
The first command, the declaration, is important: The subsequent commands assign strings into the three cells within the array object named chest.

To print the contents, write a foreach-loop that enumerates the array's elements:

foreach ( string s in chest )
    { Console.WriteLine(s); }
Here is a more elaborate printout:
for( int i = 0;  i != chest.Length;  i = i + 1 ) 
    { Console.WriteLine("drawer " + i + " holds " + chest[i]); }

Here is a more complete example. Recall the vote-counting example from Chapter 5, where the votes for 4 candidates (Candidate 0, 1, 2, and 3) were tallied by a loop program, which updates a four-celled list. The Python coding was this:

================================================

votes = 4 * [0]   # holds the votes for the 4 candidates
# collect the votes:
processing = True
while processing
    v = int(raw_input("Type your vote (0,1,2,3): ")
    if v < 0 or v > 3 :
        processing = False   # bad vote, so quit
    else :
        votes[v] = votes[v] + 1
# print the totals:
count = 0
while count != len(votes) :
    print "Candidate", count, "has", votes[count], "votes"
    count = count + 1

========================================================

Here is the corresponding C# version:

=========================================================

using System;

public class Votes {
  public static void Main() {
    int[] votes = {0, 0, 0, 0};  // holds the votes for the 4 candidates
    bool processing = true;
    // collect the votes:
    while ( processing )
          { Console.Write("Type your vote (0,1,2,3): ");
            int v = Int32.Parse(Console.ReadLine());
            if ((v < 0) || (v > 3))
               { processing = false; } // bad vote, so quit
            else
               { votes[v] = votes[v] + 1; }
          }
    // print the totals: 
    for( int i = 0;  i != votes.Length;  i = i + 1 )
       { Console.WriteLine("Candidate " + i + " has " + votes[i] + " votes"); }
  }
}

=========================================================

C# arrays can be grids, too. Recall the slide-puzzle game from Lecture 5 --- it used a 4-by-4 grid of integers and was constructed like this in Python:

puzzle = [ [15, 14, 13, 12],
           [11, 10,  9,  8],
           [ 7,  6,  5,  4],
           [ 3,  2,  1,  0] ]
We have two ways of building the grid in C#. The first is called a two-dimensional array:
int[,] puzzle = { {15, 14, 13, 12},    // use braces, not square brackets,
                  {11, 10,  9,  8},    //  to initialize an array
                  { 7,  6,  5,  4},
                  { 3,  2,  1,  0} };
We index the array with two integers, separated by a comma, e.g., puzzle[1,3] refers to the element in row 1, column 3: 8.

Here is the Python coding of the slide-puzzle program, followed by its C# version:

=============================================

# SlidePuzzle implements a slide-puzzle game.

PUZZLE_SIZE = 4
# the puzzle, where 0 marks the empty space/cell:
puzzle = [ [15, 14, 13, 12],
           [11, 10,  9,  8],
	   [ 7,  6,  5,  4],
	   [ 3,  2,  1,  0] ]

# it is convenient for us to remember which cell is empty:
empty_space = (3, 3)

# loop forever to move the pieces as dictated by the user:
while True :
    # print the puzzle:
    for row in puzzle :
        print row

    # ask for the next move:
    num = int(raw_input("\nType number of piece to move: "))

    # search the puzzle for the piece numbered  num:
    piece = ()  # remembers the cell coordinates where  num  rests
    for i in range(PUZZLE_SIZE):
        for j in range(PUZZLE_SIZE) :
	    if num == puzzle[i][j] :
	        piece = (i, j)  # we found it at cell i,j
	        break

    # The previous loops are finished; did we find the piece ?
    if piece == () :
        print "illegal move --- try again"
    else :  # we did find it, so let's try to move it:
        # First, check if the piece is adjacent to the empty space: 
	i = piece[0] 
	j = piece[1]
	if (empty_space == (i-1, j))    \
	   or (empty_space == (i+1, j)) \
	   or (empty_space == (i, j-1)) \
	   or (empty_space == (i, j+1)) :
	    # True, so it's ok to move  num  into the empty space:
            puzzle[empty_space[0]][empty_space[1]] = num
	    # remember that the former location of  num  is now empty:
	    puzzle[i][j] = 0
	    empty_space = (i,j)
        else : # False, so it's not ok to move  num:
	    print "illegal move --- try again"

===============================================
FIGURE 7=====================================================

using System;
/// <remarks> SlidePuzzle implements a slide-puzzle game.
///   assumed input: a series of integers between 1 and 15, which indicate the
///     pieces to move in the slide puzzle.
///   guaranteed output: the slide puzzle, as it appears after each move 
/// <remarks>
public class SlidePuzzle
{ public static void Main()
  {
    int PUZZLE_SIZE = 4;  // how large we make the puzzle
    // the puzzle, where 0 marks the empty space/cell:
    int[,] puzzle = { {15, 14, 13, 12},      // use braces, not brackets,
                       {11, 10,  9,  8},     //  to initialize an array
	               { 7,  6,  5,  4},
		       { 3,  2,  1,  0} };
    int empty_x = 3;
    int empty_y = 3;  // the x,y-coordinates of the empty space

    while (true) {
        // print the puzzle:
        int count = 0;
        foreach ( int n in puzzle )
          { Console.Write(n);
            Console.Write(" ");
            count = count + 1;
            if ( count % PUZZLE_SIZE == 0 )
               { Console.WriteLine(); }
          }
       // ask for the next move:
       Console.Write("Type number of piece to move : ");
       int num = Int32.Parse(Console.ReadLine());
       // search the puzzle for the piece numbered  num:
       int found_x = -1;
       int found_y = -1;
       for ( int i = 0;  i != PUZZLE_SIZE;  i = i + 1 )
           { for ( int j = 0;  j != PUZZLE_SIZE; j = j + 1 )
		 { if ( num == puzzle[i,j] )
		      { found_x = i;
		        found_y = j;
			break;  // we found  num  at cell i,j
                      }
		 }
           }
       // The previous loops are finished; did we find the piece ?
       if ( found_x == -1 )
          { Console.WriteLine("illegal move --- try again"); }
       else // we did find it, so let's try to move it:
          { // First, check if the piece is adjacent to the empty space
	    if (  ((empty_x == found_x-1) && (empty_y == found_y))
	       || ((empty_x == found_x+1) && (empty_y == found_y))
	       || ((empty_x == found_x) && (empty_y == found_y-1))
	       || ((empty_x == found_x) && (empty_y == found_y+1)) )
               // If true, it's ok to move  num  into the empty space:
	       { puzzle[empty_x,empty_y] = num;
	         puzzle[found_x,found_y] = 0;
		 empty_x = found_x; 
		 empty_y = found_y;
               }
            else { Console.WriteLine("illegal move --- try again"); }
	  }
    }  // end while-loop
  }
}

ENDFIGURE=========================================================

Because C# does not have tuples, we cannot save the coordinates of the puzzle's empty space as a pair. Note also that the elements of a two-dimensional array can be processed by a single foreach-loop: the elements are enumerated in row-major order (all of row 0, then all of row 1, etc.).

We can also model the puzzle as a nested array, but we cannot use the convenient set-braces notation to initialize the nested array --- we must write for-loops:

int[][] puzzle = new int[4][];  
for ( int row = 0;  row < 4;  row = row + 1 )
{ puzzle[row] = new int[4];
  for ( int col = 0;  col < 4;  col = col + 1 )
      { puzzle[row][col] = 16 - ((row * 4) + col + 1); }
}

When we print the nested array, we use nested loops:

foreach (int[] row in puzzle )
  { foreach (int num in row )
      { Console.Write(num);  Console.Write(" "); }
    Console.WriteLine();
  }
An individual element is indexed just like in Python, with two bracketed indexes, e.g.,
puzzle[found_x][found_y] = 0;

What about dictionaries? Can we easily simulate them in C#? Well, no. In the next course, CIS300, you will learn how to build a hash table implementation of a dictionary in C#. (Or, you can use the version prewritten for you in the .NET library,)


99.5 Functions

Functions in C# operate like the ones in Python. Here are three simple examples from Lecture 6.1, reformatted into C#, along with a Main function that calls them:

Figure 8=========================================

using System;
/// <remarks> ExampleFunctions defines and tests three functions. </remarks>
public class ExampleFunctions
{
  /// <summary> error  prints an annoying error message  </summary>
  public static void error()
  { string text = "ErrorErrorErrorErrorErrorErrorError\n";
    Console.WriteLine(text + text + text);
  }

  /// <summary> printBigError prints an error message surrounded
  ///   by an annoying border. </summary>
  /// <param name="message"> the error message </param>
  public static void printBigError(string message)
  { error();
    Console.WriteLine(message);
    error();
  }

  /// <summary> reciprocal  computes the reciprocal of its argument </summary>
  /// <param name="n"> an int, which must not equal 0 </param>
  /// <returns> 1.0/n   (When  n==0,  0.0 is returned.) </returns>
  public static double reciprocal(int n)
  { double answer = 0.0;
    if ( n != 0 )
       { answer = (1.0 / n); }
    return answer;
  }

  /// <summary>Main tests the functions. </summary>
  public static void Main()
  { double d = reciprocal(5);
    printBigError("one fifth is " + d + " ?!"); 
  }
}

=================================================

Note that

C# functions are best used to build modules, so we move directly to this subject.


99.6 In C#, a module is a class

When we write a C# class that holds the main function, we are, in effect, writing the controller module of a program. Recall there is another form of module, the data-structure (or ``model'') module, which holds a data structure (like an array) and the functions that maintain it. We now study how to write a model module.

Here is the clock module, slightly changed, from Lecture 6.2:

=========================================================

# Clock models a clock that ticks and emits an alert every 60 ticks

time = 0  # holds the time

def tick() :
  """tick  increments the time by one tick and emits an alert every 60 ticks"""
  global time
  time = time + 1
  if (time % 60) == 0 :
      print "Clock alert: time is", time

def getTime() :
  """getTime  returns the current time"""
  global time
  return time

def reset(new_time) :
  """reset  resets the clock to the  new_time"""
  global time
  time = new_time

=================================================

Now, here is its recoding in C#:

FIGURE 9======================================================

using System;

/// <remarks> Clock models a clock that ticks and emits an alert 
///   every 60 ticks. </remarks>
public class Clock
{  private int time;  // the time

  /// <summary> This is the ''constructor'' (__init__) method, 
  ///  which sets the starting values of the variables. </summary>
  /// <param name="init_time"> the initial time for the clock. </param>
  public Clock(int init_time)
  { time = init_time; }

  /// <summary> tick increments the clock by one tick </summary>
  public void tick()
  {  time = time + 1;
     if ((time % 60) == 0 )
        { Console.WriteLine("Alert: " + time); }
  }

  /// <summary> getTime returns the current time. </summary>
  /// <returns> the current time </returns>
  public int getTime()
  { return time; }

  /// <summary> reset resets the clock to a new time </summary>
  /// <param name="new_time"> the time to reset </param>
  public void reset(int new_time)
  { time = new_time; }
}

ENDFIGURE=====================================================
The variable, time, and its maintenance functions are listed within the brackets of class Clock. The coding looks as expected, but notice the keyword, private, in the definition,
private int time;  // the time
The word private asserts that variable time cannot be referenced from outside the class. Indeed, only tick and getTime can be referenced, because they are labelled by the keyword, public.

Also, we have added an ``init'' function to the class, which must have the same name as the class itself. The function is called a constructor method, and it is used to initialize the private variables. There is no need for a Main function.

Finally, notice the removal of the static keywords from the header lines of the functions. (Again, use static to label functions that are called from Main.)

The Clock module will be used as a helper module to another program. So, we save it in the file, Clock.cs, and we compile it, like this:

csc /debug+ /t:library Clock.cs
The extra notation, /t:library, says that the ``target code'' produced by the C# compiler will be a library file (a .dll-file) that can be connected to another program. When the compiler finishes, it produces the file, Clock.dll.

To use class Clock, study this test program:

FIGURE 10==================================================

using System;
using System.Threading;  // a module used for the ``sleep'' trick below

/// <remarks> TestClock shows a ticking clock.  </remarks>
public class TestClock
{  public static void Main()
   { int START = 50;
     Clock c = new Clock(START);  // construct the clock, named  c
     
     while ( true )
         { // tell the clock to tick and display the time:
           c.tick();
           Console.WriteLine(c.getTime());

	   // Here is a trick that
	   //   makes the program stop (sleep) for 1000 milliseconds:
           Thread.Sleep(1000);
         }
     // alas, we _cannot_ do this:
     //   Console.WriteLine(c.time);  
     // this is illegal because variable  time  in  Clock  is _private_
   }
}

==========================================================
The program shows that we activate a ``module'' with this command:
Clock c = new Clock();
which names the module, c. We use the module's components in the usual way:
c.tick();
Console.WriteLine(c.getTime());
but we cannot use the module's private variable; this is illegal:
Console.WriteLine(c.time); 
C# makes good use of the private keyword to prevent unacceptable updates of the variables in a module --- only the module's public functions are allowed to update the private variables.

As usual, we save the program in a file, TestClock.cs. When we compile the program, we tell the C# compiler to link the clock module to the program:

csc /debug+ /R:Clock.dll TestClock.cs
The result will be an executable program, TestClock.exe.


99.6.1 ``Static'' modules

There is one other way to construct a module in C# --- this is the ``static'' version:

  1. In Figure 9, remove the constructor (init) method. Also, change the variable declaration to read
    private static int time = 0;
    

  2. Next, insert keyword static into the header lines of the two functions.

    This makes Clock into a file-of-functions, like one would write in the language, C.

    The static module looks like this:

    FIGURE 11===============================
    using System;
    public class Clock1
    { private static int time;  // the time
    
      public static void tick()
      {  time = time + 1;
         if ((time % 60) == 0 )
            { Console.WriteLine("Alert: " + time); }
      }
    
      public static int getTime()
      { return time; }
    
      public static void reset(int new_time)
      { time = new_time; }
    }
    ====================================
    Now, the functions can be called directly by a Main function.

  3. Rewrite Figure 10 without c --- use Clock1 instead. The program looks like this:
    FIGURE 12=================================
    using System;  using System.Threading; 
    public class TestClock1 {
      public static void Main()
      { while ( true ) {
              Clock1.tick();
              Console.WriteLine(Clock1.getTime());
              Thread.Sleep(1000);
            }
      }
    }
    ===================================
This style was used within System's Console module, which includes the functions ReadLine, WriteLine, etc., that we have already used.


99.7 Objects are constructed from classes

In the previous section, in Figures 9 and 10, we used a C# class to construct a Clock ``module.'' Actually, what we did was to construct one Clock object and use it just like we would use a module.

Given class Clock seen earlier, we might write the following test program, which builds 3 clock objects and synchronizes them to count seconds, minutes, and hours:

FIGURE 13=====================================================

/// <remarks> TestClock2 shows how to construct 3 clocks that are 
///   synchronized to count seconds, minutes, and hours.  </remarks>
public class TestClock2
{  public static void Main()
   { Clock hours = new Clock(0);
     Clock minutes = new Clock(0);
     Clock seconds = new Clock(0);

     while ( true )
         { // tell the seconds clock to tick:
           seconds.tick();
           if ( seconds.getTime() == 60 )  // one full minute?
              { minutes.tick();
                seconds.reset(0);
              }
           if ( minutes.getTime() == 60 )  // one full hour?
              { hours.tick();
                minutes.reset(0);
              }
           Console.WriteLine(hours.getTime() + ":" + minutes.getTime()
                             + ":" + seconds.getTime());
           // make the program sleep 100 milliseconds:
           Thread.Sleep(100);
         }
   }
}

ENDFIGURE=========================================================
We constructed 3 separate clocks (and 3 namespaces in computer storage) from
     Clock hours = new Clock(0);
     Clock minutes = new Clock(0);
     Clock seconds = new Clock(0);


99.7.1 Subclasses and method override

The Smiley example from Lecture 8 of the Python notes showed how to build a class that can be extended by a subclass. In C#, the starting class is called the base class. Here is the Python coding of class Smiley:
======================================================

class Smiley(object) :
    """Smiley  objects know their name and age."""

    def __init__(self, new_name) : 
        """__init__ constructs a new Smiley object.

           It holds two variables:  name, a string
                                    age, an integer (initialized to 0).
           parameters:  self - the new object we are constructing
                        new_name - the string that we assign to variable,  name
        """
        self.name = new_name   # make the object hold a  name
        self.age = 0           # make the object hold an  age
        print "Smiley object named", self.name, "constructed." # print trace info

    def talk(self) :
        """talk  makes an object say its name and age.

           parameter: self - the object that will talk
        """
        print "(-:  I am", self.name, "and I am", self.age, "days old."
        self.age = self.age + 1

    def tellSecret(self) :
        """tellSecret makes an object tell a ``secret message''
            parameters: self - the object that will tell the secret
            returns: a string, the secret
        """
        return "I don't know"

==========================================================

Here it is, reformatted in C#:

FIGURE 14================================================

using System;
/// <remarks> Smiley objects know their name and age </remarks>
public class Smiley
{
  private String name;  // holds the object's name
  private int age;      // holds the object's age

  /// <summary> Constructor  Smiley  constructs a new Smiley. </summary>
  /// <param name="new_name">the object's name </param>
  public Smiley(String new_name)
  { name = new_name;
    age = 0;
    Console.WriteLine("Smiley object named " + name + " constructed.");
  }

  /// <summary> talk  makes an object say its name and age </summary>
  public void talk() 
  { Console.WriteLine("(-: I am " + name + " and I am " + age
                       + " days old.");
    age = age + 1;
  }

  /// <summary> tellsecret makes an object tell a ``secret message'' </summary>
  /// <returns> a string, the secret </returns>
  public virtual String tellSecret() 
  { return "I don't know"; }
}

ENDFIGURE=====================================================

The changes in the C# version are

C# tries to hide the details about self; the result is that C# classes are coded to look like modules!

Here is how we use a C# class to construct three Smiley objects:

FIGURE 15===============================================

public class TestSmiley
{  public static void Main()
   { Smiley s1 = new Smiley("larry");
     s1.talk();
     s1.talk();

     Smiley s2 = new Smiley("moe");
     s2.talk();

     s1.talk();
     Smiley s3 = new Smiley("curly");
     s1.talk();
     s2.talk();
     s3.talk();
  }
}

==============================================
Note the use of the keyword, new, each time a new object is constructed.

Finally, here is the SmartSmiley class from Python, which showed how one class can build upon (extend) another:

===============================================

from TextSmiley import Smiley

class SmartSmiley(Smiley) :   # this class extends class Smiley
    """SmartSmiley is a Smiley that can also remember and tell secrets."""

    def __init__(self, new_name, my_secret) :
        """__init__ constructs a new SmartSmiley object

           It holds the variables from a Smiley -- name and age -- along with
             a new variable,  secret.
           parameters:  self - the new object we are constructing
                        new_name - a string that we assign to  name
                        my_secret - a string that we assign to  secret
        """
        Smiley.__init__(self, new_name)  # call Smiley's __init__ function
        self.secret = my_secret          # define a new variable,  secret

    def tellSecret(self) :  # this cancels the version of  tellSecret in Smiley
        """tellSecret makes an object tell a ``secret message''.

           parameters: self - the object that will tell the secret
           returns: a string, the secret
        """
        return self.secret

    def learnsSecretOf(self, anotherSmiley) :
        """learnsSecretOf  lets one object learn another object's secret message

           parameters: self - the object that will learn a new secret
              anotherSmiley - a Smiley object that will surrender its secret
        """
        self.secret = self.secret + ", and " + anotherSmiley.tellSecret()

def test():
  s1 = SmartSmiley("larry", "eat your vegetables")
  s1.talk()
  print s1.tellSecret()

  s2 = SmartSmiley("moe", "get enough sleep")
  s1.learnsSecretOf(s2)
  print s1.tellSecret()

  s3 = Smiley("curly")
  print s3.tellSecret()

  s1.learnsSecretOf(s3)
  print s1.tellSecret()

  s3.learnsSecretOf(s1)

  Smiley.__init__(s3,"ed")
  print Smiley.tellSecret(s3)

test()

===========================================================
In C#, we have
FIGURE 16===================================================

/// <remarks>SmartSmiley objects are Smileys that remember secrets. </remarks>
public class SmartSmiley : Smiley   // the base class is Smiley
{ 
  private String secret;  // the secret remembered by a SmartSmiley

  /// <summary>Constructor SmartSmiley makes a new SmartSmiley. </summary>
  /// <param name="new_name"> the object's name </param>
  /// <param name="my_secret"> this object's secret </param>
  public SmartSmiley(String new_name, String my_secret) : base(new_name)
    // The previous phrase calls the constructor (init) method for Smiley.
  { secret = my_secret;}

  /// <summary> an improved coding of tellSecret for SmartSmileys </summary>
  public override String tellSecret()
  { return secret; }

  /// <summary> learnsSecretOf  asks another Smiley for its secret and
  ///   remembers it.  </summary>
  /// <param name="anotherSmiley">the Smiley asked to tell its secret </param>
  public void learnsSecretOf(Smiley anotherSmiley)
  { secret = secret + ", and " + anotherSmiley.tellSecret(); }
}

ENDFIGURE=======================================================
Notice

Some test code:

FIGURE 17==========================================================

using System;
public class TestSmart
{ public static void Main()
  { SmartSmiley s1 = new SmartSmiley("larry", "eat your vegetables");
    s1.talk();
    Console.WriteLine(s1.tellSecret());

    SmartSmiley s2 = new SmartSmiley("moe", "get enough sleep");
    s1.learnsSecretOf(s2);
    Console.WriteLine("larry says: " + s1.tellSecret());

    Smiley s3 = new Smiley("curly");
    Console.WriteLine("curley says: " + s3.tellSecret());
    s1.learnsSecretOf(s3);
    Console.WriteLine("moe says: " + s1.tellSecret());
    //  We cannot do this:
    // s3.learnsSecretOf(s1);
  }
}

=============================================================