- 1 A First C# Program
- 2 Namespaces
- 3 Alternative Forms of the Main() Function
- 4 Making a Statement
- 5 Opening a Text File for Reading and Writing
- 6 Formatting Output
- 7 The string Type
- 8 Local Objects
- 9 Value and Reference Types
- 10 The C# Array
- 11 The new Expression
- 12 Garbage Collection
- 13 Dynamic Arrays: The ArrayList Collection Class
- 14 The Unified Type System
- 15 Jagged Arrays
- 16 The Hashtable Container
- 17 Exception Handling
- 18 A Basic Language Handbook for C#
1.4 Making a Statement
The first thing we need to do is determine if the user specified any arguments. We do this by asking args the number of elements it contains.3 For our program I decided that if the user doesn't supply the necessary command-line arguments, the program shuts down. (As an exercise, you may wish to reimplement the program to allow the user to interactively enter the desired options. The program is certainly friendlier that way.)
In my implementation, if args is empty, the program prints an explanation of the correct way to invoke WordCount, then exits using a return statement. (The return statement causes the function in which it occurs to terminatethat is, to return to the location from which it was invoked.)
public static void Main( string [] args ) { if ( args.Length == 0 ) { display_usage(); return; } }
Length is a property of an array. It holds a count of the number of elements currently stored in the array. The test of Length is placed within the conditional test of the C# if statement. If the test evaluates to true, the statement immediately following the test is executed; otherwise it is ignored. If multiple statements need be executed, as in our example, they must be enclosed in curly braces (the text within the braces is called a statement block).
A common mistake that beginners make is to forget the statement block when they wish to execute two or more statements: 4
// this is an incorrect usage of the if statement if ( args.Length == 0 ) display_usage(); return;
The indentation of return reflects the programmer's intention. It does not, however, reflect the program's behavior. Without the statement block, only the function is conditionally executed; the return statement is executed whether or not the array is empty.
The return statement can also return a value. This value becomes the return value of the functionfor example,
public static int Main( string [] args ) { if ( args.Length == 0 ) { display_usage(); return -1; // indicate failure } }
The rule is that the value following the return statement must be compatible with the return type of the function. Compatible can mean one of two things. In the simplest case, the value being returned is the same type as that indicated as the return type of the function. The value -1, for example, is of type int. The second meaning of compatible requires that an implicit conversion exist between the actual return value and the function's return type.
The if-else statement allows us to select between alternative statements on the basis of the truth of a particular condition. The else clause represents a statement or statement block to be executed if the tested condition evaluates to false. For example, if we chose not to immediately return on discovering an empty args array, we could provide the following if-else statement instead:
if ( args.Length == 0 ) display_usage(); else { /* do everything else here ... */ }
To access the individual command-line options, we'll use a foreach loop to iterate across the array, reading each element in turn. For example, the following loop statement prints each option to the user's console:
foreach ( string option in args ) Console.WriteLine( option );
option is a read-only string object. It is visible only within the body of the foreach statement. Within each iteration of the loop, option is set to refer to the next element of the args array.
For our program, we'll compare each string element against the set of supported options. If the string does not match any of the options, we'll check to see if the string represents a text file. Whenever we are testing a series of mutually exclusive conditions, as we are in this case, we typically combine the tests into a chain of if-else-if statementsfor example,
bool traceOn = false, bool spyOn = false; foreach ( string option in args ) { if ( option.Equals( "-t" )) traceOn = true; else if ( option.Equals( "-s" )) spyOn = true; else if ( option.Equals( "-h" )) { display_usage(); return; } else check_valid_file_type( option ); }
The bool keyword represents a Boolean data type that can be assigned the literal values true or false. In our example, traceOn and spyOn represent two Boolean objects initialized to false.
Equals() is a nonstatic member function of string. Nonstatic member functions (also referred to as instance member functions) are invoked through an instance of the class for which the function is a memberin this case, the string object option. The expression
option.Equals( "-t" )
instructs the compiler to invoke the string instance method Equals() to compare the string stored within option with the string literal "-t". If the two are equal, Equals() returns true; otherwise, it returns false.
If the mutually exclusive conditions are constant expressions5, we can turn our chain of if-else-if statements into the somewhat more readable switch statementfor example,
foreach ( string option in args ) switch ( option ) { case "-t": traceOn = true; break; case "-s": spyOn = true; break; case "-h": display_usage(); return; default: check_valid_file_type( option ); break; }
The switch statement can be used to test a value of an integral type, a char type, an enumeration, or a string type. The switch keyword is followed by the expression enclosed in parentheses. A series of case labels follows the switch keyword, each specifying a constant expression. Each case label must specify a unique value.
The result of the expression is compared against each case label in turn. If there is a match, the statements following the case label are executed. If there is no match and the default label is present, the statements associated with the default label are executed. If there is no match and no default label, nothing happens. (There can be only one default label.)
Each nonempty case label must be followed either by a break statement or by another terminating statement, such as a return or a throw; otherwise a compiler error results. (throw passes program control out of the current function into the runtime exception-handling mechanism. We look at exception handling in Section 1.17. The break statement passes program control to the statement following the terminating curly brace of the switch statement.)
An empty case label is the one exception to this rule. It does need not have a break statement. We do this typically when multiple values require the same actionfor example,
switch ( next_char ) { case 'a': case 'A': acnt++; break; // to illustrate an alternative syntax ... case 'e': case 'E': ecnt++; break; // ... the other vowels case '\0': return; // OK default: non_vowel_cnt++; break; }
If we wish to execute the body of two case labelsthe first of which is not emptywe must use a special goto statement that targets either an explicit case label or the default label:
switch ( text_word ) { case "C#": case "c#": csharp_cnt++; goto default; case "C++": case "c++": cplus_cnt++; goto default; case "Java": case "java": goto case "C#"; default: word_cnt++; break; }