CHAPTER 31

image

Other Language Details

This chapter deals with some miscellaneous details about the language, including how to use the Main() function, how the preprocessor works, and how to write literal values.

The Main Function

The simplest version of the Main() function should already be familiar from earlier in the book.

using System;
class Test
{
    public static void Main()
    {
       Console.WriteLine("Hello, Universe!");
    }
}

Returning an Int Status

It may sometimes be useful to return a status from the Main() function, particularly if the program is called programmatically, because the return status can be used to determine whether the application executed successfully. This is done by declaring the return type of Main() as an integer.

using System;
class Test
{
    public static int Main()
    {
       Console.WriteLine("Hello, Universe!");
       return(0);
    }
}

Command-Line Parameters

The command-line parameters to an application can be accessed by declaring the Main() function with a string array as a parameter. The parameters can then be processed by indexing the array.1

using System;
class Test
{
    public static void Main(string[] args)
    {
       foreach (string arg in args)
       {
            Console.WriteLine("Arg: {0}", arg);
       }
    }
}

Multiple Mains

It is sometimes useful for testing purposes to include a static function in a class that tests the class to make sure it does the right thing. In C#, this static test function can be written as a Main() function, which makes automating such tests easy.

If there is a single main() function encountered during a compilation, the C# compiler will use it. If there is more than one main()function, the class that contains the desired Main() can be specified on the command line with the /main:<classname> option.

// error
using System;
class Complex
{
    static int Main()
    {
       // test code here
       Console.WriteLine("Console: Passed");
       return(0);
    }
}
class Test
{
    public static void Main(string[] args)
    {
       foreach (string arg in args)
       {
            Console.WriteLine(arg);
       }
    }
}

Compiling this file with /main:Complex will use the test version of Main(), whereas compiling with /main:Test will use the real version of Main(). Compiling it without either will result in an error.

The Main() declared in the Complex type isn’t declared public. In fact, there is no requirement that Main() should be public, and keeping it private is useful in cases such as these, where the test function shouldn’t be visible to users of the class.

Preprocessing

The most important thing to remember about the C# preprocessor is that it doesn’t exist. The features in the C/C++ processor are either totally absent or present in a limited form. In the “absent” category are include files and the ability to do text replacement with #define. The #ifdef and associated directives are present in a slightly modified form and can be used to control the compilation of code.

Getting rid of the macro version (something like #define MYINT int) of #define allows the programmer to understand more clearly what the program is saying. A name that isn’t familiar must come from one of the namespaces, and there’s no need to hunt through include files to find it. The C# code means what it says it means.2

Getting rid of macro preprocessing and #include also enables a simplified compilation structure and faster compilation, and there is no need to create a separate header file and keep it in sync with the implementation file.

When C# source files are compiled, the order of the compilation of the individual files is unimportant, and it is equivalent to them all being in one big file. There is no need for forward declarations or worrying about the order of #includes.

Preprocessing Directives

The preprocessing directives that are supported include those shown in Table 31-1.

Table 31-1. C# Preprocessing Directives

Directive Description
#define identifier Defines an identifier. Note that a value can’t be set for it; it can merely be defined. Identifiers can also be defined via the command line.
#undef identifier Undefines an identifier.
#if expression Code in this section is compiled if the expression is true.
#elif expression Else-if construct. If the previous directive wasn’t taken and the expression is true, code in this section is compiled.
#else If the previous directive wasn’t taken, code in this section is compiled.
#endif Marks the end of a section.

Here’s an example of how they might be used:

#define DEBUGLOG
using System;
class Test
{
    public static void Main()
    {
       #if DEBUGLOG
       Console.WriteLine("In Main – Debug Enabled");
       #else
       Console.WriteLine("In Main – No Debug");
       #endif
    }
}

#define and #undef must precede any “real code” in a file, or an error occurs. The previous example can’t be written as follows:

// error
using System;
class Test
{
    #define DEBUGLOG
    public static void Main()
    {
       #if DEBUGLOG
       Console.WriteLine("In Main – Debug Enabled");
       #else
       Console.WriteLine("In Main – No Debug");
       #endif
    }
}

C# also supports the Conditional attribute for controlling function calls based upon preprocessor identifiers; see Chapter 39 for more information.

Preprocessor Expressions

The operators shown in Table 31-2 can be used in preprocessor expressions.

Table 31-2. C# Preprocessor Expressions

Operator Description
! ex Expression is true if ex is false
ex == value Expression is true if ex is equal to value
ex != value Expression is true if ex is not equal to value
ex1 && ex2 Expression is true if both ex1 and ex2 are true
ex1 || ex2 Expression is true if either ex1 or ex2 are true

Parentheses can be used to group expressions.

#if !(DEBUGLOG && (TESTLOG || USERLOG))

If TESTLOG or USERLOG is defined and DEBUGLOG is defined, then the expression within the parentheses is true, which is then negated by the !.

Inline Warning Control

The C# compiler provides the ability to suppress compiler warnings using the /nowarn switch. Warnings that are suppressed on the command line are disabled for all files, but it may be desirable in some situations to disable a single instance of a warning. For example, if a program uses a method that has been marked as obsolete:

class Program
{
    static void Main(string[] args)
    {
       ObsoleteMethod();
    }
    [Obsolete]
    static void ObsoleteMethod()
    {
    }
}

the program will generate a warning message:

'Program.ObsoleteMethod()' is obsolete

In this case, it may be useful to disable that warning message, since the presence of an expected warning can hide other warnings.3 This can be done by adding a #pragma warning disable directive to the program.

class Program
{
    static void Main(string[] args)
    {
#pragma warning disable 612
       ObsoleteMethod();
#pragma warning restore 612
    }
    [Obsolete]
    static void ObsoleteMethod()
    {
    }
}

The disable directive turns off the warning, and then the restore directive turns it back on. It is possible to disable multiple warnings by separating the warning numbers by commas.

Other Preprocessor Functions

In addition to the #if and #define functions, there are a few other preprocessor functions that can be used.

#warning and #error

#warning and #error allow warnings or errors to be reported during the compilation process. All text following #warning or #error will be output when the compiler reaches that line.

For a section of code, you could do the following:

#warning Check algorithm with John

This would result in the string “Check algorithm with John” being output when the line was compiled.

#line

The #line directive provides two different capabilities.

First, it can be used to specify the line number and/or source filename to report when the compiler encounters warnings or errors. This can be useful in cases where user-written code is folded in with user-generated code; by specifying the line number and the filename of the user-written code, the user will see the error in their code, not the combined file. Either the line number or both the line number and the filename can be specified.

#line 255#line 300 "UserCode.cs"

The line number and filename can be reset to the default behavior using this:

#line 255 default

Second, #line can also be used to hide data from debugger stepping.

#line hidden

The debugger will then skip all subsequent lines until another #line directive is encountered.4 This is also very useful if the code is machine-generated.

#region

The #region and #endregion directives are used to group code into named sections, which can then be collapsed or expanded inside an editor. For example, if a class implements the IXmlSerializable interface, it can be useful to group the implementation methods in a region.

class Employee: IXmlSerializable
{
    public string Name { get; set; }
    public Decimal Age { get; set; }
    public Employee(String name, Decimal age)
    {
       Name = name;
       Age = age;
    }
    #region IXmlSerializable
    // IXmlSerializable methods go here. . .
    #endregion
}

This region can then be collapsed so it is out of the way when looking at the other members of the type.

Lexical Details

The lexical details of the language deal with things that are important at the single-character level: how to write numerical constants, identifiers, and other low-level entities of the language.

Identifiers

An identifier is a name that is used for some program element, such as a variable or a function.

When using code that has been written in other languages, some names might be C# keywords. To write such a name, an “at” character (@) can be placed before the name, which merely indicates to C# that the name is not a keyword but an identifier.

Identifiers must have a letter or an underscore as the first character, and the remainder of the identifier can also include numeric characters.5 Unicode characters can be specified using udddd, where dddd specifies the hex value of the Unicode character.

Similarly, use @ to use keywords as identifiers.

class Test
{
    public void @checked()
    {
    }
}

This class declares a member function named checked. Using this feature so that identifiers can be the same as built-in identifiers is not recommended because of the confusion it can create.

Keywords

Keywords are reserved words that cannot be used as identifiers. The keywords in C# are shown in Table 31-3.

Table 31-3. C# Keywords

image

C# also defines contextual keywords. A contextual keyword can be used as a variable, class, or property name, but when it is used in a specific context, it becomes a keyword. The partial contextual keyword is used in the specification of a partial class, where is used to define generic contraints, yield is used to implement an iterator, and value is used in a set property block to access the incoming value. Table 31-4 lists the contextual keywords.

Table 31-4. C# Contextual Keywords

image

Literals

Literals are the way in which values are written for variables.

Boolean

There are two boolean literals: true and false.

Integer

Integer literals are written simply by writing the numeric value. Integer literals that are small enough to fit into the int data type6 are treated as ints; if they are too big to fit into an int, they will be created as the smallest type of uint, long, or ulong in which the literal will fit.

Here are some integer literal examples:

123
−15

Integer literals can also be written in hexadecimal format, by placing 0x in front of the constant.

0xFFFF
0x12AB

Real

Real literals are used for the types float, double, and decimal. Float literals have f or F after them; double literals have d or D after them and are the default when nothing is specified, and decimal literals have m or M after them.

Exponential notation can be used by appending e followed by the exponent to the real literal.

Here are some examples:

1.345                   // double constant
−8.99e12F                // float constant
15.66m                   // decimal constant

Character

A character literal is a single character enclosed in single quotes, such as x. Table 31-5 shows the supported escape sequences.

Table 31-5. Character Escape Sequences

Escape Sequence Description
' Single quote.
" Double quote. Double quotes do not need to be escaped if they are part of a character literal.
\ Backslash.
Null.
a Alert.
 Backspace.
f Form feed.
Newline.
Carriage return.
Tab.
v Vertical tab.
xdddd Character dddd, where d is a hexadecimal digit.
udddd Unicode character dddd, where d is a hexadecimal digit.
Udddddddd Unicode character dddddddd, where d is a hexadecimal digit.

String

String literals are written as a sequence of characters enclosed in double quotes, such as "Hello". All of the character escape sequences are supported within strings.

Strings cannot span multiple lines, but the same effect can be achieved by concatenating them.

string s = "What is your favorite color?" +
             "Blue. No, Red. ";

When this code is compiled, a single string constant will be created, consisting of the two strings concatenated.

Verbatim Strings

Verbatim strings allow some strings to be specified more simply.

If a string contains the backslash character, such as a filename, a verbatim string can be used to turn off the support for escape sequences. Instead of writing something like this:

string s = "c:\Program Files\Microsoft Office\Office";

the following can be written:

string s = @"c:Program FilesMicrosoft OfficeOffice";

The verbatim string syntax is also useful if the code is generated by a program and there is no way to constrain the contents of the string. All characters can be represented within such a string, though any occurrence of the double-quote character must be doubled.

string s = @"She said, ""Hello""";

In addition, strings that are written with the verbatim string syntax can span multiple lines, and any whitespace (spaces, tabs, and newlines) is preserved.

using System;
class Test
{
    public static void Main()
    {
       string s = @"
       C: Hello, Miss?
       O: What do you mean, 'Miss'?
       C: I'm Sorry, I have a cold. I wish to make a complaint.";
       Console.WriteLine(s);
    }
}

Comments

Comments in C# are denoted by a double slash for a single-line comment, and /* and */ denote the beginning and ending of a multiline comment.

// This is a single-line comment
/*
 * Multiline comment here
 */

C# also supports a special type of comment that is used to associate documentation with code; those comments are described in Chapter 38.

Expanding Internal Accessibility

The C# accessibility options (public, protected, internal, and private) generally provide sufficient flexibility in allowing other classes access to a class’s members. There are two scenarios where the provided accessibilities are insufficient.

  • A group of classes that are all conceptually in one namespace may be split across two assemblies if one class is large and rarely used.
  • Tests are being written against a class, and the developer would prefer to keep the class methods internal.

C# provides a way to modify the specified accessibility. The InternalsVisibleTo attribute can be used to specify that the classes in another assembly be treated as if they were in the same assembly when establishing accessibility. The attribute is declared as follows:

[assembly: InternalsVisibleTo("ParserUnitTest")]
internal class Parser
{
    internal void Parse(string input);
}

A class in the ParserUnitTest assembly is now allowed to call Parser.Parse(). If one of the assemblies is signed, they both must be signed, and the public key of the assembly must be listed in the attribute.

[assembly: InternalsVisibleTo(
    "ParserUnitTest, PublicKey=<public-key-of-ParserUnitTest>")]

1 There is no command-line parser in .NET, but there are many free libraries available.

2 In C++, you can define true to 0 and false to 1 if you want.

3 It’s a pretty good idea to use the /warnaserror compiler option, which doesn’t allow you to ignore any warnings. In that case, you may need to disable some warnings.

4 There is no #line show to directly turn off hidden, so you are stuck using #line default or setting the specific line number or filename.

5 It’s actually a fair bit more complicated than this, since C# has Unicode support. Briefly, letters can be any Unicode letter character, and characters other than the underscore (_) can also be used for combinations. See the online C# Language Reference for a full description.

6 See the “Basic Data Types” section in Chapter 3.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.3.111