Now that some basic examples and the framework of Visual C# 2008 code have been presented, we can discuss the fundamental building blocks of any C# application. This section starts by discussing symbols and tokens, the most elemental components of a Visual C# 2008 application.
Symbols and tokens are the basic constituents of the C# language. C# statements consist of symbols and tokens—indeed, they cannot be assembled without them. Table 1-2 provides a list of the C# symbols and tokens. Each entry in Table 1-2 is explained in the text that follows.
Table 1-2. C# Symbols and tokens
Description | Symbols or tokens |
---|---|
White space | Space, Form Feed |
Tab | Horizontal_tab, Vertical_tab |
Punctuator | . , : ; |
Line terminator | Carriage return, line feed, next line character, line separator, paragraph separator, carriage return and line feed together |
Comment | // /* */ /// /** */ |
Preprocessor directive | # |
Block | {} |
Lambda expression | => |
Generics | < > |
Nullable type | ? |
Character | Unicode_character |
Escape character | code |
Integer suffix (case-insensitive) | u l ul lu |
Real suffix (case-insensitive) | f d m |
Operator | + - * % / > < ? ?? ( ) [ ] | || ^ ! ~ ++ -- = is as & && -> :: << >> |
Compound operator | == != <= >= += -= *= /= %= &= |= ^= <<= >>= => |
White space is defined as a space, horizontal tab, vertical tab, or form feed character. White space characters can be combined; where one whitespace character is required, two or more contiguous characters of white space can be substituted.
Punctuators separate and delimit elements of the C# language. Punctuators include the semicolon (;), dot (.), colon (:), and comma (,).
In Visual C#, statements are terminated with a semicolon (;). C# is a free-form language in which a statement can span multiple lines of source code and can start in any position. Conversely, multiple statements can be combined on a single source code line. Here are some variations:
int variablea = variableb + variablec; variableb = variableb + 3; variablec = variablec + 1; ++variableb;
Dot syntax connotes membership. The dot character (.) binds a target to a member, in which the target can be a namespace, type, structure, enumeration, interface, or object. This assumes the member is accessible. Membership is sometimes nested and described with additional dots.
Here is the syntax for the dot punctuator:
Target.Member
This is an example of the dot punctuator:
System.Windows.Forms.MessageBox.Show("A nice day!");
System, Windows, and Forms are namespaces. MessageBox is a class. Show, the most nested member, is a static method.
The colon punctuator primarily delimits a label, indicates inheritance, indicates interface implementation, sets a generic constraint, or is part of a conditional operator.
Labels are tags for locations to which program execution can be transferred. A label is terminated with a colon punctuator (:). The scope of a label is limited to the containing block and any nested block. There are various methods for transferring to a label. For example, you can jump to a label with the goto statement. Within a switch block, you also can use the goto statement to jump to a case or default statement.
Here is the syntax for the label punctuator:
label_identifier: statement
A statement must follow a label, even if it’s an empty statement.
Here is an example of a goto statement:
public static void Main() { goto one; // do stuff one: Console.WriteLine("one"); }
The comma punctuator delimits array indexes, function parameters, types in an inheritance list, statement clauses, and other language elements. The comma punctuator separates clauses of a for statement in the following code:
for (int iBottom = 1, iTop = 10; iBottom < iTop; ++iBottom, --iTop) { Console.WriteLine("{0}x{1} {2}", iBottom, iTop, iBottom*iTop); }
A statement clause is a substatement in which multiple statement clauses can be combined into a single statement. Statement clauses are not always available—check documentation related to the language artifact to be sure.
Line terminators separate lines of source code. Where one line terminator is available, two or more are allowed. Except in string literals, line terminators can be inserted anywhere white space is allowed. The following code is syntactically incorrect:
int variableb, variablec; int variablea = var iableb+variablec; // wrong!
The variableb identifier cannot contain spaces. Therefore, it also cannot contain a line terminator.
C# supports four styles of comments: single-line, delimited, single-line documentation, and multi-line documentation comments. Although comments are not required, the liberal use of comments is considered good programming style. Be kind to those maintaining your program (present and future) —comment! I highly recommend reading Code Complete, Second Edition (Microsoft Press, 2004), by Steve McConnell; this book provides valuable best practices on programming, including how to document source code properly.
Single-line comments start at the comment symbol and conclude at the line terminator, as follows:
Console.WriteLine(objGreeting.French); // Display Hello (French)
Delimited comments, also called multi-line or block comments, are bracketed by the /* and */ symbols. Delimited comments can span multiple lines of source code:
/* Class Program: Programmer Donis Marshall */ class Program { static int Main(string[] args) { Greeting objGreeting = new Greeting(); Console.WriteLine(objGreeting.French); // Display Hello (French) return 0; } }
Documentation comments apply a consistent format to source code comments and use XML tags to classify comments. With the documentation generator, documentation comments are exportable to an XML file. The resulting file is called the documentation file, which is identified in the Visual Studio project options. IntelliSense and the Object Browser use information in this file.
Single-line documentation comments are partially automated in the Visual Studio IDE. The Visual Studio IDE has Smart Comment Editing, which automatically continues or creates a skeleton for a document comment after initially entering the /// symbol. For example, the following code snippet shows sample code with single-line documentation comments. After entering an initial ///, Smart Comment Editing completed the remainder of the comment framework, including adding comments and XML tags for the type, methods, method parameter, and return value. You only need to update the comment framework with specific comments and additional comment tags that might be helpful:
/// <summary> /// /// </summary> class Program { /// <summary> /// /// </summary> /// <param name="args"></param> /// <returns></returns> static int Main(string[] args) { Greeting objGreeting = new Greeting(); Console.WriteLine(objGreeting.French); // Display Hello (French) return 0; } }
Here are the documentation comments with added details:
/// <summary> /// Starter class for Simple HelloWorld /// </summary> class Program { /// <summary> /// Program Entry Point /// </summary> /// <param name="args">Command Line Parameters</param> /// <returns>zero</returns> static int Main(string[] args) { Greeting objGreeting = new Greeting(); Console.WriteLine(objGreeting.French); // Display Hello (French) return 0; } }
The C# compiler is a documentation generator. The /doc compiler option instructs the compiler to generate the documentation file. This can be done using the Visual Studio IDE. Select Project Properties from the Project menu. In the Properties window, select the Build tab. Toward the bottom of the Build pane (shown in Figure 1-2), you can specify the name of the XML documentation file.
For the preceding source file, this is the documentation file generated by the C# compiler:
<?xml version="1.0"?> <doc> <assembly> <name>ConsoleApplication1</name> </assembly> <members> <member name="T:ConsoleApplication1.Program"> <summary> Starter class for Simple HelloWorld </summary> </member> <member name="M:ConsoleApplication1.Program.Main(System.String[])"> <summary> Program Entry Point </summary> <param name="args">Command Line Parameters</param> <returns>zero</returns> </member> </members> </doc>
The documentation generator prefixes IDs to element names of the member name tag. In the preceding documentation file, T is the prefix for a type, whereas M is a prefix for a method. Here’s a listing of IDs:
E | Event |
F | Field |
M | Method |
N | Namespace |
P | Property |
T | Type |
! | Error |
Multi-line documentation tags are an alternative to single-line documentation tags. Smart Comment Editing is not available with multi-line documentation tags. You must enter the documentation tags explicitly. However, Intellisense is available. Multi-line documentation comments must adhere to a degree of consistency, which is explained in the article titled "Delimiters for Documentation Tags (C# Programming Guide)," in Visual Studio Help (ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_csref/html/9b2bdd18-4f5c-4c0b-988e-fb992e0d233e.htm).
Here is an example of delimited documentation tags:
/** *<summary>this is an example.</summary> */
Preprocessor directives define symbols, undefine symbols, include source code, exclude source code, name sections of source code, and set warning and error conditions. The variety of preprocessor directives is limited compared with C++, and many of the C++ preprocessor directives are not available in C#. There is not a separate preprocessor or compilation stage for preprocessor statements. Preprocessor statements are processed by the normal C# compiler. The term preprocessor is used because the preprocessor directives are semantically similar to related commands in C++.
Here is the syntax for a preprocessor directive:
#command expression
This is a list of preprocessor directives available in C#:
#define
#else
#line
#region
#undef
#elif
#error
#endregion
#if
#endif
#warning
#pragma
The preprocessor symbol (#) and subsequent directive are optionally separated with white space but must be on the same line. A preprocessor directive can be followed with a single-line comment but not a multi-line comment.
The declarative preprocessor directives are #define and #undef, which define and undefine a preprocessor symbol, respectively. Defined symbols are implicitly true, whereas undefined symbols are false. Declarative symbols must be defined in each compilation unit where the symbol is referenced. Undeclared symbols default to undefined and false. The #define and #undef directives must precede any source code. Redundant #define and #undef directives have no effect.
Preprocessor symbols can also be set as a compiler option. In the Build pane of the Project Properties dialog box, you can define one or more symbols in the Conditional Compilation Symbols text box. This is shown in Figure 1-3. Other preprocessor symbols, such as DEBUG and TRACE, are commonly set implicitly as a compiler option.
Here is the syntax for declarative preprocessor directives:
#define identifier #undef identifier
Conditional preprocessor directives are the #if, #else, #elif, and #endif directives, which exclude or include source code. A conditional preprocessor directive begins with #if and ends with #endif. The intervening conditional preprocessing directives, #else and #elif, are optional.
Here is the syntax for conditional preprocessor directives:
#if Boolean_expression #elif Boolean_expression #else #endif
The Boolean_expression of the #if and #elif directive is a combination of preprocessor symbols and Boolean operators (! == != && ||). If the Boolean_expression is true, the source code immediately after the #if or #elif directive and before the next conditional preprocessor directive is included in the compilation. If the Boolean_expression is false, the source code is excluded from source compilation. The #else directive can be added to a #if or #elif combination. If the Boolean_expression of #if and #elif is false, the code following the #else is included in the compilation. When true, the source code after the #else is not included. Here’s sample code with preprocessor symbols and related directives:
#define DEBUGGING using System; namespace Donis.CSharpBook { class Starter{ #if DEBUGGING static void OutputLocals() { Console.WriteLine("debugging..."); } #endif static void Main() { #if DEBUGGING OutputLocals(); #endif } } }
Finally, the #elif directive is a combination of an else and if conditional preprocessor directive. It is matched with the nearest #if directive:
#if expression source_code #elif expression source_code #else source_code #endif
Diagnostic directives include the #error, #warning, and #pragma directives. The #error and #warning directives display error and warning messages, respectively. The diagnostic messages are displayed in the Error List window of the Visual Studio IDE. Similar to standard compilation errors, an #error directive prevents the program from compiling successfully; a #warning directive does not prevent the program from successfully compiling unless Treat Warnings As Error is set as a compiler option. You can use conditional directives to conditionally apply diagnostic directives.
Here is the syntax for diagnostic directives:
#error error_message #warning error_message
The error_message is of string type and is optional.
The #pragma directive disables or enables compilation warnings. When disabled, the warning or error is suppressed and will not appear in the Error List window. This is useful for suppressing an unwanted warning or error temporarily or permanently.
Here is the syntax for pragma directives:
#pragma warning disable warning_list #pragma warning restore warning_list
The warning_list contains one or more warnings delimited with commas. A disabled warning remains disabled until it is restored or the compilation unit ends.
The following code demonstrates the pragma warning directive. In this example, the 219 warning (Variable Is Assigned But Its Value Is Never Used) is initially disabled and then restored. Therefore a warning is received about variableb but not variablea.
class Starter { #pragma warning disable 219 static void Main() { int variablea = 10; } #pragma warning restore 219 static void FuncA() { int variableb = 20; } }
Region directives mark sections of source code. The #region directive starts a region, whereas the #endregion directive ends the region. Region directives can be nested. The Visual Studio IDE outlines the source code based on region directives. In Visual Studio, you can collapse or expand regions of source code.
Here is the syntax for region directives:
#region identifier source_code #endregion
Line directives modify the line number reported in subsequent compiler errors and warnings. There are three versions of the line directive.
Here is the syntax for line directives:
#line line_number source_filename
#line default
#line hidden
The first #line directive renumbers the source code from the location of the directive until the end of the compilation unit is reached or overridden by another #line directive. In the following code, the #line directive resets the current reporting line to 25:
#line 25 static void Main() { Console.WriteLine("#line application"); int variablea=10; // 219 warning }
The #line default directive undoes any previous #line directives. The line number is then reset to the natural line number.
The #line hidden directive is only tangentially related to the line number. This directive does not affect the line number; it hides source code from the debugger when stepping. The source code is skipped until the next #line directive is encountered. This is helpful in stepping through source code. Hidden code is essentially stepped over. For example, when stepping in the following source, the for loop is stepped over. The #line default directive then returns normal stepping.
static void Main() { int variablea = 10; variablea++; #line hidden for (int i = 0; i < 5; ++i) { variablea++; } #line default Console.WriteLine(variablea); }
A type, which can be a class, struct, interface, or enum, is defined within a block. Members of the type are contained inside the block.
Here is the syntax for a type block:
type typename { // block
}
A block can also be a statement block. Statement blocks contain one or more statements. Each statement of the statement block is delimited by a semicolon. Typically, where a single statement is allowed, a statement block can be substituted. Statement blocks are commonly used as function bodies, conditional statements, and iterative statements. For function bodies, a statement block is required.
The if path in the following code consists of a single statement. Therefore, a statement block is not required. The Console.WriteLine is the only statement within the context of the if statement:
static void Main() { int variablea = 5, variableb = 10; if (((variablea + variableb) % 2) == 0) Console.WriteLine("the sum is even"); }
In the modified code, the if path contains multiple statements and a statement block is needed. Some would suggest, and I agree, that always using statement blocks with conditional or iterative statements is a good practice. This prevents an inadvertent future error when a single statement is expanded to multiple statements as shown below, but the block is forgotten:
static void Main() { int variablea = 5, variableb = 10; if (((variablea + variableb) % 2) == 0) { Console.WriteLine("{0} {1}", variablea, variableb); Console.WriteLine("the sum is even"); } }
A generic is an abstraction of a type, which itself is an abstraction of a noun, place, or thing.
The NodeInt class is an abstraction of a node within a linked list of integers. The following is a partial implementation of the code (the full implementation is presented later in this book):
class NodeInt { public NodeInt(int f_Value, NodeInt f_Previous) { m_Value = f_Value; m_Previous = f_Previous; } // Remaining methods private int m_Value; private NodeInt m_Previous; }
The Node generic type further abstracts a linked list. Unlike NodeInt, Node is not integer-specific but a linked list of any type. In the generic type, integer specifics of the NodeInt class have been removed and substituted with placeholders.
class Node<T> { public Node(T f_Value, Node<T> f_Previous) { m_Value = f_Value; m_Previous = f_Previous; } // Remaining methods private T m_Value; private Node<T> m_Previous; }
In the preceding example, T is the generic type parameter, which is then used as a placeholder throughout the class for future type substitution.
There is much more about generics later in Chapter 7.
C# source files contain Unicode characters, which are the most innate of symbols. Every element, keyword, operator, or identifier in the source file is a composite of Unicode characters.
Numeric suffixes cast a literal value to the underlying or a related type. Literal integer values can have the l, u, ul, and lu suffixes appended to them; literal real values can have the f, d, and m suffixes added. The suffixes are case-insensitive. Table 1-3 describes each suffix.
Table 1-3. Description of suffixes
Description | Type | Suffix |
---|---|---|
Unsigned integer or unsigned long | uint or ulong | U |
Long or unsigned long | long or ulong | L |
Unsigned long | ulong | ul or lu |
Float | float | F |
Double | double | D |
Money | decimal | M |
When casting a real type using the m suffix (for monetary or currency calculations), rounding might be required. If so, banker’s rounding is used.
Here is an example of a numeric suffix:
uint variable = 10u;
The escape character provides an alternate means of encoding Unicode characters, which is particularly useful for special characters that are not available on a standard keyboard. Escape sequences can be embedded in identifiers and string literals. Unicode escape sequences must have four hexadecimal digits and are limited to a single character.
A Unicode escape sequence looks like this:
u hexdigit1 hexdigit2 hexdigit3 hexdigit4
Hexadecimal escape sequences contain one or more digits as defined by the location of a Unicode character.
A hexadecimal escape sequence looks like this:
x hexdigit1 hexdigit2 ... hexdigitn
Table 1-4 shows a list of the predefined escape sequences in C#.