Chapter 1. Application Development

This chapter covers some of the fundamental activities you will need to perform when developing your C# solutions. The recipes in this chapter describe how to do the following:

  • Use the C# command-line compiler to build console and Windows Forms applications (recipes 1-1 and 1-2)

  • Create and use code modules and libraries (recipes 1-3 and 1-4)

  • Access command-line arguments from within your applications (recipe 1-5)

  • Use compiler directives and attributes to selectively include code at build time (recipe 1-6)

  • Access program elements built in other languages whose names conflict with C# keywords (recipe 1-7)

  • Give assemblies strong names and verify strong-named assemblies (recipes 1-8, 1-9, 1-10, and 1-11)

  • Sign an assembly with a Microsoft Authenticode digital signature (recipes 1-12 and 1-13)

  • Manage the shared assemblies that are stored in the global assembly cache (recipe 1-14)

  • Prevent people from decompiling your assembly (recipe 1-15)

  • Manipulate the appearance of the console (recipe 1-16)

  • Create static, anonymous, and dynamically expandable types (recipes 1-17, 1-18, and 1-19)

  • Define automatically implemented properties (recipe 1-20)

  • Overload an operator and implement a custom conversion operator (recipes 1-21 and 1-22)

  • Handle an event with an anonymous function (recipe 1-23)

  • Implement a customer indexer (recipe 1-24)

Note

All the tools discussed in this chapter ship with the Microsoft .NET Framework or the .NET Framework software development kit (SDK). The tools that are part of the .NET Framework are in the main directory for the version of the framework you are running. For example, they are in the directory C:WINDOWSMicrosoft.NETFrameworkv4.0 (or C:WINDOWSMicrosoft.NETFramework64v4.0 for 64-bit systems) if you install version 4.0 of the .NET Framework to the default location. The .NET installation process automatically adds this directory to your environment path. The tools provided with the SDK are in the Bin subdirectory of the directory in which you install the SDK, which is C:Program FilesMicrosoft SDKsWindowsv7.0ain (or C:Program Files(x86)Microsoft SDKsWindowsv7.0ain on a 64-bit system) if you chose the default path during the installation of Microsoft Visual Studio 2010. This directory is not added to your path automatically, so you must manually edit your path in order to have easy access to these tools. Most of the tools support short and long forms of the command-line switches that control their functionality. This chapter always shows the long form, which is more informative but requires additional typing. For the shortened form of each switch, see the tool's documentation in the .NET Framework SDK.

Create a Console Application from the Command Line

Problem

You need to use the C# command-line compiler to build an application that does not require a Windows graphical user interface (GUI) but instead displays output to, and reads input from, the Windows command prompt (console).

Solution

In one of your classes, ensure you implement a static method named Main with one of the following signatures:

public static void Main();
public static void Main(string[] args);
public static int Main();
public static int Main(string[] args);

Build your application using the C# compiler (csc.exe) by running the following command (where HelloWorld.cs is the name of your source code file):

csc /target:exe HelloWorld.cs

Note

If you own Visual Studio, you will most often use the Console Application project template to create new console applications. However, for small applications, it is often just as easy to use the command-line compiler. It is also useful to know how to build console applications from the command line if you are ever working on a machine without Visual Studio and want to create a quick utility to automate some task.

How It Works

By default, the C# compiler will build a console application unless you specify otherwise. For this reason, it's not necessary to specify the /target:exe switch, but doing so makes your intention clearer, which is useful if you are creating build scripts that will be used by others.

To build a console application consisting of more than one source code file, you must specify all the source files as arguments to the compiler. For example, the following command builds an application named MyFirstApp.exe from two source files named HelloWorld.cs and ConsoleUtils.cs:

csc /target:exe /main:HelloWorld /out:MyFirstApp.exe HelloWorld.cs ConsoleUtils.cs

The /out switch allows you to specify the name of the compiled assembly. Otherwise, the assembly is named after the first source file listed-HelloWorld.cs in the example. If classes in both the HelloWorld and ConsoleUtils files contain Main methods, the compiler cannot automatically determine which method represents the correct entry point for the assembly. Therefore, you must use the compiler's /main switch to identify the name of the class that contains the correct entry point for your application. When using the /main switch, you must provide the fully qualified class name (including the namespace); otherwise, you will get a CS1555 compilation error: "Could not find 'HelloWorld' specified for Main method."

If you have a lot of C# code source files to compile, you should use a response file. This simple text file contains the command-line arguments for csc.exe. When you call csc.exe, you give the name of this response file as a single parameter prefixed by the @ character—for example:

csc @commands.rsp

To achieve the equivalent of the previous example, commands.rsp would contain this:

/target:exe /main:HelloWorld /out:MyFirstApp.exe HelloWorld.cs ConsoleUtils.cs

The Code

The following code lists a class named ConsoleUtils that is defined in a file named ConsoleUtils.cs:

using System;

namespace Apress.VisualCSharpRecipes.Chapter01
{
    public class ConsoleUtils
    {
        // A method to display a prompt and read a response from the console.
public static string ReadString(string msg)
        {
            Console.Write(msg);
            return Console.ReadLine();
        }

        // A method to display a message to the console.
        public static void WriteString(string msg)
        {
            Console.WriteLine(msg);
        }

        // Main method used for testing ConsoleUtility methods.
        public static void Main()
        {
            // Prompt the reader to enter a name.
            string name = ReadString("Please enter your name : ");

            // Welcome the reader to Visual C# 2010 Recipes.
            WriteString("Welcome to Visual C# 2010 Recipes, " + name);
        }
    }
}

The HelloWorld class listed next uses the ConsoleUtils class to display the message "Hello, world" to the console (HelloWorld is contained in the HelloWorld.cs file):

using System;

namespace Apress.VisualCSharpRecipes.Chapter01
{
    class HelloWorld
    {
        public static void Main()
        {
            ConsoleUtils.WriteString("Hello, world");

            Console.WriteLine("
Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Usage

To build HelloWorld.exe from the two source files, use the following command:

csc /target:exe /main:Apress.VisualCSharpRecipes.Chapter01.HelloWorld
/out:HelloWorld.exe ConsoleUtils.cs HelloWorld.cs

Create a Windows-Based Application from the Command Line

Problem

You need to use the C# command-line compiler to build an application that provides a Windows Forms–based GUI.

Solution

Create a class that extends the System.Windows.Forms.Form class. (This will be your application's main form.) In one of your classes, ensure you implement a static method named Main. In the Main method, create an instance of your main form class and pass it to the static method Run of the System.Windows. Forms.Application class. Build your application using the command-line C# compiler, and specify the /target:winexe compiler switch.

Note

If you own Visual Studio, you will most often use the Windows Application project template to create new Windows Forms–based applications. Building large GUI-based applications is a time-consuming undertaking that involves the correct instantiation, configuration, and wiring up of many forms and controls. Visual Studio automates much of the work associated with building graphical applications. Trying to build a large graphical application without the aid of tools such as Visual Studio will take you much longer, be extremely tedious, and result in a greater chance of bugs in your code. However, it is also useful to know the essentials required to create a Windows-based application using the command line in case you are ever working on a machine without Visual Studio and want to create a quick utility to automate some task or get input from a user. In order to build a WPF application from the command line, you must use the MSBuild tool—see the MSBuild reference in the .NET Framework documentation.

How It Works

Building an application that provides a simple Windows GUI is a world away from developing a full-fledged Windows-based application. However, you must perform certain tasks regardless of whether you are writing the Windows equivalent of Hello World or the next version of Microsoft Word, including the following:

  • For each form you need in your application, create a class that extends the System.Windows.Forms.Form class.

  • In each of your form classes, declare members that represent the controls that will be on that form, such as buttons, labels, lists, and text boxes. These members should be declared private or at least protected so that other program elements cannot access them directly. If you need to expose the methods or properties of these controls, implement the necessary members in your form class, providing indirect and controlled access to the contained controls.

  • Declare methods in your form class that will handle events raised by the controls contained by the form, such as button clicks or key presses when a text box is the active control. These methods should be private or protected and follow the standard .NET event pattern (described in recipe 13-11). It's in these methods (or methods called by these methods) where you will define the bulk of your application's functionality.

  • Declare a constructor for your form class that instantiates each of the form's controls and configures their initial state (size, color, position, content, and so on). The constructor should also wire up the appropriate event handler methods of your class to the events of each control.

  • Declare a static method named Main—usually as a member of your application's main form class. This method is the entry point for your application, and it can have the same signatures as those mentioned in recipe 1-1. In the Main method, call Application.EnableVisualStyles to allow Windows theme support, create an instance of your application's main form, and pass it as an argument to the static Application.Run method. The Run method makes your main form visible and starts a standard Windows message loop on the current thread, which passes the user input (key presses, mouse clicks, and so on) to your application form as events.

The Code

The Recipe01-02 class shown in the following code listing is a simple Windows Forms application that demonstrates the techniques just listed. When run, it prompts a user to enter a name and then displays a message box welcoming the user to Visual C# 2010 Recipes.

using System;
using System.Windows.Forms;

namespace Apress.VisualCSharpRecipes.Chapter01
{
    class Recipe01_02 : Form
    {
        // Private members to hold references to the form's controls.
        private Label label1;
        private TextBox textBox1;
        private Button button1;

        // Constructor used to create an instance of the form and configure
// the form's controls.
        public Recipe01_02()
        {
            // Instantiate the controls used on the form.
            this.label1 = new Label();
            this.textBox1 = new TextBox();
            this.button1 = new Button();

            // Suspend the layout logic of the form while we configure and
            // position the controls.
            this.SuspendLayout();

            // Configure label1, which displays the user prompt.
            this.label1.Location = new System.Drawing.Point(16, 36);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(148, 16);
            this.label1.TabIndex = 0;
            this.label1.Text = "Please enter your name:";

            // Configure textBox1, which accepts the user input.
            this.textBox1.Location = new System.Drawing.Point(172, 32);
            this.textBox1.Name = "textBox1";
            this.textBox1.TabIndex = 1;
            this.textBox1.Text = "";

            // Configure button1, which the user clicks to enter a name.
            this.button1.Location = new System.Drawing.Point(109, 80);
            this.button1.Name = "button1";
            this.button1.TabIndex = 2;
            this.button1.Text = "Enter";
            this.button1.Click += new System.EventHandler(this.button1_Click);

            // Configure WelcomeForm, and add controls.
            this.ClientSize = new System.Drawing.Size(292, 126);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.label1);
            this.Name = "form1";
            this.Text = "Visual C# 2010 Recipes";

            // Resume the layout logic of the form now that all controls are
            // configured.
            this.ResumeLayout(false);
        }

        // Event handler called when the user clicks the Enter button on the
        // form.
        private void button1_Click(object sender, System.EventArgs e)
        {
            // Write debug message to the console.
            System.Console.WriteLine("User entered: " + textBox1.Text);
// Display welcome as a message box.
            MessageBox.Show("Welcome to Visual C# 2010 Recipes, "
                + textBox1.Text, "Visual C# 2010 Recipes");
        }

        // Application entry point, creates an instance of the form, and begins
        // running a standard message loop on the current thread. The message
        // loop feeds the application with input from the user as events.
        [STAThread]
        public static void Main()
        {
            Application.EnableVisualStyles();
            Application.Run(new Recipe01_02());
        }
    }
}

Usage

To build the Recipe01-02 class into an application, use this command:

csc /target:winexe Recipe01-02.cs

The /target:winexe switch tells the compiler that you are building a Windows-based application. As a result, the compiler builds the executable in such a way that no console is created when you run your application. If you use the /target:exe switch to build a Windows Forms application instead of /target:winexe, your application will still work correctly, but you will have a console window visible while the application is running. Although this is undesirable for production-quality software, the console window is useful if you want to write debug and logging information while you're developing and testing your Windows Forms application. You can write to this console using the Write and WriteLine methods of the System.Console class.

Figure 1-1 shows the WelcomeForm.exe application greeting a user named Rupert. This version of the application is built using the /target:exe compiler switch.

A simple Windows Forms application

Figure 1.1. A simple Windows Forms application

Create and Use a Code Module

Problem

You need to do one or more of the following:

  • Improve your application's performance and memory efficiency by ensuring that the runtime loads rarely used types only when they are required

  • Compile types written in C# to a form you can build into assemblies being developed in other .NET languages

  • Use types developed in another language and build them into your C# assemblies

Solution

Build your C# source code into a module by using the command-line compiler and specifying the /target:module compiler switch. To incorporate an existing module into your assembly, use the /addmodule compiler switch.

How It Works

Modules are the building blocks of .NET assemblies. Modules consist of a single file that contains the following:

  • Microsoft Intermediate Language (MSIL) code created from your source code during compilation

  • Metadata describing the types contained in the module

  • Resources, such as icons and string tables, used by the types in the module

Assemblies consist of one or more modules and an assembly manifest. When a single module exists, the module and assembly manifest are usually built into a single file for convenience. When more than one module exists, the assembly represents a logical grouping of more than one file that you must deploy as a complete unit. In these situations, the assembly manifest is either contained in a separate file or built into one of the modules.

By building an assembly from multiple modules, you complicate the management and deployment of the assembly, but under some circumstances, modules offer significant benefits:

  • The runtime will load a module only when the types defined in the module are required. Therefore, where you have a set of types that your application uses rarely, you can partition them into a separate module that the runtime will load only if necessary. This offers the following benefits:

    • Improving performance, especially if your application is loaded across a network

    • Minimizing the use of memory

  • The ability to use many different languages to write applications that run on the Common Language Runtime (CLR) is a great strength of the .NET Framework. However, the C# compiler can't compile your Microsoft Visual Basic .NET or COBOL .NET code for inclusion in your assembly. To use code written in another language, you can compile it into a separate assembly and reference it. But if you want it to be an integral part of your assembly, then you must build it into a module. Similarly, if you want to allow others to include your code as an integral part of their assemblies, you must compile your code as modules. When you use modules, because the code becomes part of the same assembly, members marked as internal or protected internal are accessible, whereas they would not be if the code had been accessed from an external assembly.

Usage

To compile a source file named ConsoleUtils.cs (see recipe 1-1 for the contents) into a module, use the command csc /target:module ConsoleUtils.cs. The result is the creation of a file named ConsoleUtils.netmodule. The netmodule extension is the default extension for modules, and the filename is the same as the name of the C# source file.

You can also build modules from multiple source files, which results in a single file (module) containing the MSIL and metadata for all types contained in all the source files. The command csc /target:module ConsoleUtils.cs WindowsUtils.cs compiles two source files named ConsoleUtils.cs and WindowsUtils.cs to create the module named ConsoleUtils.netmodule. The module is named after the first source file listed unless you override the name with the /out compiler switch. For example, the command csc /target:module /out:Utilities.netmodule ConsoleUtils.cs WindowsUtils.cs creates a module named Utilities.netmodule.

To build an assembly consisting of multiple modules, you must use the /addmodule compiler switch. To build an executable named MyFirstApp.exe from two modules named WindowsUtils.netmodule and ConsoleUtils.netmodule and two source files named SourceOne.cs and SourceTwo.cs, use the command csc /out:MyFirstApp.exe /target:exe /addmodule:WindowsUtils.netmodule,ConsoleUtils.netmodule SourceOne.cs SourceTwo.cs. This command will result in an assembly consisting of the following files:

  • MyFirstApp.exe, which contains the assembly manifest as well as the MSIL for the types declared in the SourceOne.cs and SourceTwo.cs source files

  • ConsoleUtils.netmodule and WindowsUtils.netmodule, which are now integral components of the multifile assembly but are unchanged by this compilation process

Warning

If you attempt to run an assembly (such as MyFirstApp.exe) without any required netmodules present, a System.IO.FileNotFoundException is thrown the first time any code tries to use types defined in the missing code module. This is a significant concern because the missing modules will not be identified until runtime. You must be careful when deploying multifile assemblies.

Create and Use a Code Library from the Command Line

Problem

You need to build a set of functionality into a reusable code library so that multiple applications can reference and reuse it.

Solution

Build your library using the command-line C# compiler, and specify the /target:library compiler switch. To reference the library, use the /reference compiler switch when you build your application, and specify the names of the required libraries.

How It Works

Recipe 1-1 showed you how to build an application named MyFirstApp.exe from the two source files ConsoleUtils.cs and HelloWorld.cs. The ConsoleUtils.cs file contains the ConsoleUtils class, which provides methods to simplify interaction with the Windows console. If you were to extend the functionality of the ConsoleUtils class, you could add functionality useful to many applications. Instead of including the source code for ConsoleUtils in every application, you could build it into a library and deploy it independently, making the functionality accessible to many applications.

Usage

To build the ConsoleUtils.cs file into a library, use the command csc /target:library ConsoleUtils.cs. This will produce a library file named ConsoleUtils.dll. To build a library from multiple source files, list the name of each file at the end of the command. You can also specify the name of the library using the /out compiler switch; otherwise, the library is named after the first source file listed. For example, to build a library named MyFirstLibrary.dll from two source files named ConsoleUtils.cs and WindowsUtils.cs, use the command csc /out:MyFirstLibrary.dll /target:library ConsoleUtils.cs WindowsUtils.cs.

Before distributing your library, you might consider strongly naming it so that nobody can modify your assembly and pass it off as being the original. Strongly naming your library also allows people to install it into the Global Assembly Cache (GAC), which makes reuse much easier. (Recipe 1-9 describes how to strongly name your assembly, and recipe 1-14 describes how to install a strongly named assembly into the GAC.) You might also consider signing your library with an Authenticode signature, which allows users to confirm you are the publisher of the assembly—see recipe 1-12 for details on signing assemblies with Authenticode.

To compile an assembly that relies on types declared within external libraries, you must tell the compiler which libraries are referenced using the /reference compiler switch. For example, to compile the HelloWorld.cs source file (from recipe 1-1) if the ConsoleUtils class is contained in the ConsoleUtils.dll library, use the command csc /reference:ConsoleUtils.dll HelloWorld.cs. Remember these four points:

  • If you reference more than one library, separate each library name with a comma or semicolon, but don't include any spaces. For example, use /reference:ConsoleUtils.dll,WindowsUtils.dll.

  • If the libraries aren't in the same directory as the source code, use the /lib switch on the compiler to specify the additional directories where the compiler should look for libraries. For example, use /lib:c:CommonLibraries,c:DevThirdPartyLibs.

  • Note that additional directories can be relative to the source folder. Don't forget that at runtime, the generated assembly must be in the same folder as the application that needs it except if you deploy it into the GAC.

  • If the library you need to reference is a multifile assembly, reference the file that contains the assembly manifest. (For information about multifile assemblies, see recipe 1-3.)

Access Command-Line Arguments

Problem

You need to access the arguments that were specified on the command line when your application was executed.

Solution

Use a signature for your Main method that exposes the command-line arguments as a string array. Alternatively, access the command-line arguments from anywhere in your code using the static members of the System.Environment class.

How It Works

Declaring your application's Main method with one of the following signatures provides access to the command-line arguments as a string array:

public static void Main(string[] args);
public static int Main(string[] args);

At runtime, the args argument will contain a string for each value entered on the command line after your application's name. Unlike C and C++, the application's name is not included in the array of arguments.

If you need access to the command-line arguments at places in your code other than the Main method, you can use the System.Environment class, which provides two static members that return information about the command line: CommandLine and GetCommandLineArgs.

The CommandLine property returns a string containing the full command line that launched the current process. Depending on the operating system on which the application is running, path information might precede the application name—older versions of Windows, such as Windows 98 and Windows ME, include this information. The GetCommandLineArgs method returns a string array containing the command-line arguments. This array can be processed in the same way as the string array passed to the Main method, as discussed at the start of this section. Unlike the array passed to the Main method, the first element in the array returned by the GetCommandLineArgs method is the file name of the application.

The Code

To demonstrate the access of command-line arguments, the Main method in the following example steps through each of the command-line arguments passed to it and displays them to the console. The example then accesses the command line directly through the Environment class.

using System;
namespace Apress.VisualCSharpRecipes.Chapter01
{
    class Recipe01_05
    {
        public static void Main(string[] args)
        {
            // Step through the command-line arguments.
            foreach (string s in args)
            {
                Console.WriteLine(s);
            }

            // Alternatively, access the command-line arguments directly.
            Console.WriteLine(Environment.CommandLine);

            foreach (string s in Environment.GetCommandLineArgs())
            {
                Console.WriteLine(s);
            }

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Usage

If you execute the Recipe01-05 example using the following command:

Recipe01-05 "one "two"    three" four 'five    six'

the application will generate the following output on the console:

one "two"    three

four

'five

six'

Recipe01-05  "one "two"    three" four 'five    six'

Recipe01-05

one "two"    three

four

'five

six'



Main method complete. Press Enter.

Notice that the use of double quotes (") results in more than one word being treated as a single argument, although single quotes (') do not. Also, you can include double quotes in an argument by escaping them with the backslash character (). Finally, notice that all spaces are stripped from the command line unless they are enclosed in double quotes.

Include Code Selectively at Build Time

Problem

You need to selectively include and exclude sections of source code from your compiled assembly.

Solution

Use the #if, #elif, #else, and #endif preprocessor directives to identify blocks of code that should be conditionally included in your compiled assembly. Use the System.Diagnostics.ConditionalAttribute attribute to define methods that should be called conditionally only. Control the inclusion of the conditional code using the #define and #undef directives in your code, or use the /define switch when you run the C# compiler from the command line.

How It Works

If you need your application to function differently depending on factors such as the platform or environment on which it runs, you can build runtime checks into the logic of your code that trigger the variations in operation. However, such an approach can bloat your code and affect performance, especially if many variations need to be supported or many locations exist where evaluations need to be made.

An alternative approach is to build multiple versions of your application to support the different target platforms and environments. Although this approach overcomes the problems of code bloat and performance degradation, it would be an untenable solution if you had to maintain different source code for each version, so C# provides features that allow you to build customized versions of your application from a single code base.

The #if, #elif, #else, and #endif preprocessor directives allow you to identify blocks of code that the compiler should include in your assembly only if specified symbols are defined at compile time. Symbols function as on/off switches; they don't have values—either the symbol is defined or it is not. The #if..#endif construct evaluates #if and #elif clauses only until it finds one that evaluates to true, meaning that if you define multiple symbols (winXP and win7, for example), the order of your clauses is important. The compiler includes only the code in the clause that evaluates to true. If no clause evaluates to true, the compiler includes the code in the #else clause.

You can also use logical operators to base conditional compilation on more than one symbol. Table 1-1 summarizes the supported operators.

Table 1.1. Logical Operators Supported by the #if..#endif Directive

Operator

Example

Description

==

#if winXP == true

Equality. Evaluates to true if the symbol winXP is defined. Equivalent to #if winXP.

!=

#if winXP != true

Inequality. Evaluates to true if the symbol winXP is not defined. Equivalent to #if !winXP.

&&

#if winXP && release

Logical AND. Evaluates to true only if the symbols winXP and release are defined.

||

#if winXP || release

Logical OR. Evaluates to true if either of the symbols winXP or release are defined.

()

#if (winXP || win7) && release

Parentheses allow you to group expressions. Evaluates to true if the symbols winXP or win7 are defined and the symbol release is defined.

Warning

You must be careful not to overuse conditional compilation directives and not to make your conditional expressions too complex; otherwise, your code can quickly become confusing and unmanageable—especially as your projects become larger.

To define a symbol, you can either include a #define directive in your code or use the /define compiler switch. Symbols defined using #define are active until the end of the file in which they are defined. Symbols defined using the /define compiler switch are active in all source files that are being compiled. To undefine a symbol defined using the /define compiler switch, C# provides the #undef directive, which is useful if you want to ensure a symbol is not defined in specific source files. All #define and #undef directives must appear at the top of your source file before any code, including any using directives. Symbols are case-sensitive.

A less flexible but more elegant alternative to the #if preprocessor directive is the attribute System.Diagnostics.ConditionalAttribute. If you apply ConditionalAttribute to a method, the compiler will ignore any calls to the method if the symbol specified by ConditionalAttribute is not defined at the calling point.

Using ConditionalAttribute centralizes your conditional compilation logic on the method declaration and means you can freely include calls to conditional methods without littering your code with #if directives. However, because the compiler literally removes calls to the conditional method from your code, your code can't have dependencies on return values from the conditional method. This means you can apply ConditionalAttribute only to methods that return void and do not use "out" modifiers on their arguments.

The Code

In this example, the code assigns a different value to the local variable platformName based on whether the winXP, win2000, winNT, or Win98 symbols are defined. The head of the code defines the symbols win2000 and release (not used in this example) and undefines the win98 symbol in case it was defined on the compiler command line. In addition, the ConditionalAttribute specifies that calls to the DumpState method should be included in an assembly only if the symbol DEBUG is defined during compilation.

#define win7
#define release
#undef  win2000

using System;
using System.Diagnostics;

namespace Apress.VisualCSharpRecipes.Chapter01
{
    class Recipe01_06
    {
        [Conditional("DEBUG")]
        public static void DumpState()
        {
Console.WriteLine("Dump some state...");
        }

        public static void Main()
        {
            // Declare a string to contain the platform name
            string platformName;

            #if winXP       // Compiling for Windows XP
                platformName = "Microsoft Windows XP";
            #elif win2000   // Compiling for Windows 2000
                platformName = "Microsoft Windows 2000";
            #elif win7     // Compiling for Windows 7
                platformName = "Microsoft Windows 7";
            #else           // Unknown platform specified
                platformName = "Unknown";
            #endif

            Console.WriteLine(platformName);

            // Call the conditional DumpState method.
            DumpState();

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter.");
            Console.Read();
        }
    }
}

Usage

To build the example and define the symbols winXP and DEBUG (not used in this example), use the command csc /define:winXP;DEBUG ConditionalExample.cs.

Notes

You can apply multiple ConditionalAttribute instances to a method in order to produce logical OR behavior. Calls to the following version of the DumpState method will be compiled only if the DEBUG or TEST symbols are defined:

[System.Diagnostics.Conditional("DEBUG")]
[System.Diagnostics.Conditional("TEST")]
public static void DumpState() {//...}

Achieving logical AND behavior is not as clean and involves the use of an intermediate conditional method, quickly leading to overly complex code that is hard to understand and maintain. The following is a quick example that requires the definition of both the DEBUG and TEST symbols for the DumpState functionality (contained in DumpState2) to be called:

[System.Diagnostics.Conditional("DEBUG")]
public static void DumpState() {
    DumpState2();
}

[System.Diagnostics.Conditional("TEST")]
public static void DumpState2() {//...}

Note

The Debug and Trace classes from the System.Diagnostics namespace use ConditionalAttribute on many of their methods. The methods of the Debug class are conditional on the definition of the symbol DEBUG, and the methods of the Trace class are conditional on the definition of the symbol TRACE.

Access a Program Element That Has the Same Name As a Keyword

Problem

You need to access a member of a type, but the type or member name is the same as a C# keyword.

Solution

Prefix all instances of the identifier name in your code with the at sign (@).

How It Works

The .NET Framework allows you to use software components developed in other .NET languages from within your C# applications. Each language has its own set of keywords (or reserved words) and imposes different restrictions on the names programmers can assign to program elements such as types, members, and variables. Therefore, it is possible that a programmer developing a component in another language will inadvertently use a C# keyword as the name of a program element. The at sign (@) enables you to use a C# keyword as an identifier and overcome these possible naming conflicts.

The Code

The following code fragment instantiates an object of type operator (perhaps a telephone operator) and sets its volatile property to true—both operator and volatile are C# keywords:

// Instantiate an operator object.
@operator Operator1 = new @operator();
// Set the operator's volatile property.
Operator1.@volatile = true;

Create and Manage Strongly Named Key Pairs

Problem

You need to create public and private keys (a key pair) so that you can assign strong names to your assemblies.

Solution

Use the Strong Name tool (sn.exe) to generate a key pair and store the keys in a file or cryptographic service provider (CSP) key container.

Note

A CSP is an element of the Win32 CryptoAPI that provides services such as encryption, decryption, and digital signature generation. CSPs also provide key container facilities, which use strong encryption and operating system security to protect any cryptographic keys stored in the container. A detailed discussion of CSPs and CryptoAPI is beyond the scope of this book. All you need to know for this recipe is that you can store your cryptographic keys in a CSP key container and be relatively confident that it is secure as long as nobody knows your Windows password. Refer to the CryptoAPI information in the platform SDK documentation for complete details.

How It Works

To generate a new key pair and store the keys in the file named MyKeys.snk, execute the command sn -k MyKeys.snk. (.snk is the usual extension given to files containing strongly named keys.) The generated file contains both your public and private keys. You can extract the public key using the command sn -p MyKeys.snk MyPublicKey.snk, which will create MyPublicKey.snk containing only the public key. Once you have this file in hands, you can view the public key using the command sn -tp MyPublicKey.snk, which will generate output similar to the (abbreviated) listing shown here:

Microsoft (R) .NET Framework Strong Name Utility  Version 2.0.50727.42

Copyright (C) Microsoft Corporation. All rights reserved.



Public key is

07020000002400005253413200040000010001002b4ef3c2bbd6478802b64d0dd3f2e7c65ee

6478802b63cb894a782f3a1adbb46d3ee5ec5577e7dccc818937e964cbe997c12076c19f2d7

ad179f15f7dccca6c6b72a



Public key token is 2a1d3326445fc02a

The public key token shown at the end of the listing is the last 8 bytes of a cryptographic hash code computed from the public key. Because the public key is so long, .NET uses the public key token for display purposes and as a compact mechanism for other assemblies to reference your public key. (Recipes 11-14 and 11-15 discuss cryptographic hash codes.)

As the name suggests, you don't need to keep the public key (or public key token) secret. When you strongly name your assembly (discussed in recipe 1-9), the compiler uses your private key to generate a digital signature (an encrypted hash code) of the assembly's manifest. The compiler embeds the digital signature and your public key in the assembly so that any consumer of the assembly can verify the digital signature.

Keeping your private key secret is imperative. People with access to your private key can alter your assembly and create a new strong name—leaving your customers unaware that they are using modified code. No mechanism exists to repudiate compromised strongly named keys. If your private key is compromised, you must generate new keys and distribute new versions of your assemblies that are strongly named using the new keys. You must also notify your customers about the compromised keys and explain to them which versions of your public key to trust—in all, a very costly exercise in terms of both money and credibility. You can protect your private key in many ways; the approach you use will depend on several factors.

Tip

Commonly, a small group of trusted individuals (the signing authority) has responsibility for the security of your company's strongly named signing keys and is responsible for signing all assemblies just prior to their final release. The ability to delay-sign an assembly (discussed in recipe 1-11) facilitates this model and avoids the need to distribute private keys to all development team members.

One feature provided by the Strong Name tool to simplify the security of strongly named keys is the use of CSP key containers. Once you have generated a key pair to a file, you can install the keys into a key container and delete the file. For example, to store the key pair contained in the file MyKeys.snk to a CSP container named StrongNameKeys, use the command sn -i MyKeys.snk StrongNameKeys. (Recipe 1-9 explains how to use strongly named keys stored in a CSP key container.)

An important aspect of CSP key containers is that they include user-based containers and machine-based containers. Windows security ensures each user can access only their own user-based key containers. However, any user of a machine can access a machine-based container.

By default, the Strong Name tool uses machine-based key containers, meaning that anybody who can log onto your machine and who knows the name of your key container can sign an assembly with your strongly named keys. To change the Strong Name tool to use user-based containers, use the command sn -m n, and to switch to machine-based stores, use the command sn -m y. The command sn -m will display whether the Strong Name tool is currently configured to use machine-based or user-based containers.

To delete the strongly named keys from the StrongNameKeys container (as well as delete the container), use the command sn -d StrongNameKeys.

Tip

You may need to start the command line with administrator privileges to use these tools, depending on the configuration of your system. Right-click the Command Prompt item in the Start menu and select "Run as administrator."

Give an Assembly a Strong Name

Problem

You need to give an assembly a strong name for several reasons:

  • So it has a unique identity, which allows people to assign specific permissions to the assembly when configuring code access security policy

  • So it can't be modified and passed off as your original assembly

  • So it supports versioning and version policy

  • So it can be installed in the GAC and shared across multiple applications

Solution

When you build your assembly using the command-line C# compiler, use the /keyfile or /keycontainer compiler switches to specify the location of your strongly named key pair. Use assembly-level attributes to specify optional information such as the version number and culture for your assembly. The compiler will strongly name your assembly as part of the compilation process.

Note

If you are using Visual Studio, you can configure your assembly to be strongly named by opening the project properties, selecting the Signing tab, and checking the Sign the Assembly box. You will need to specify the location of the file where your strongly named keys are stored—Visual Studio does not allow you to specify the name of a key container.

How It Works

To strongly named an assembly using the C# compiler, you need the following:

  • A strongly named key pair contained either in a file or a CSP key container. (Recipe 1-8 discusses how to create strongly named key pairs.)

  • Compiler switches to specify the location where the compiler can obtain your strongly named key pair:

    • If your key pair is in a file, use the /keyfile compiler switch and provide the name of the file where the keys are stored. For example, use /keyfile:MyKeyFile.snk.

    • If your key pair is in a CSP container, use the /keycontainer compiler switch and provide the name of the CSP key container where the keys are stored. For example, use /keycontainer:MyKeyContainer.

  • To optionally specify the culture that your assembly supports by applying the attribute System.Reflection.AssemblyCultureAttribute to the assembly. (You can't specify a culture for executable assemblies because executable assemblies support only the neutral culture.)

  • To optionally specify the version of your assembly by applying the attribute System.Reflection.AssemblyVersionAttribute to the assembly.

The Code

The executable code that follows (from a file named Recipe01-09.cs) shows how to use the optional attributes (shown in bold text) to specify the culture and the version for the assembly:

using System;
using System.Reflection;

[assembly:AssemblyCulture("")]
[assembly:AssemblyVersion("1.1.0.5")]

namespace Recipe01_09
{
    class Recipe01_09
    {
public static void Main()
        {
            Console.WriteLine("Welcome to Visual C# 2010 Recipes");

            // Wait to continue.
            Console.WriteLine("
Main method complete. Press Enter.");
            Console.Read();
        }
    }
}

Usage

To create a strongly named assembly from the example code, create the strongly named keys and store them in a file named MyKeyFile using the command sn -k MyKeyFile.snk. Then install the keys into the CSP container named MyKeys using the command sn -i MyKeyFile.snk MyKeys. You can now compile the file into a strongly named assembly using the command csc /keycontainer:MyKeys Recipe01-09.cs.

Verify That a Strongly Named Assembly Has Not Been Modified

Problem

You need to verify that a strongly named assembly has not been modified after it was built.

Solution

Use the Strong Name tool (sn.exe) to verify the assembly's strong name.

How It Works

Whenever the .NET runtime loads a strongly named assembly, the runtime extracts the encrypted hash code that's embedded in the assembly and decrypts it with the public key, which is also embedded in the assembly. The runtime then calculates the hash code of the assembly manifest and compares it to the decrypted hash code. This verification process will identify whether the assembly has changed after compilation.

If an executable assembly fails strong name verification, the runtime will display an error message or an error dialog box (depending on whether the application is a console or Windows application). If executing code tries to load an assembly that fails verification, the runtime will throw a System.IO.FileLoadException with the message "Strong name validation failed," which you should handle appropriately.

As well as generating and managing strongly named keys (discussed in recipe 1-8), the Strong Name tool allows you to verify strongly named assemblies. To verify that the strongly named assembly Recipe01-09.exe is unchanged, use the command sn -vf Recipe01-09.exe. The -v switch requests the Strong Name tool to verify the strong name of the specified assembly, and the -f switch forces strong name verification even if it has been previously disabled for the specified assembly. (You can disable strong name verification for specific assemblies using the -Vr switch, as in sn -Vr Recipe01-09.exe; see recipe 1-11 for details about why you would disable strong name verification.)

Tip

You may need to start the command line with administrator privileges to use this tool, depending on the configuration of your system. Right-click the Command Prompt item in the Start menu and select "Run as administrator."

If the assembly passes strong name verification, you will see the following output:

Microsoft (R) .NET Framework Strong Name Utility  Version 2.0.50727.42

Copyright (C) Microsoft Corporation. All rights reserved.



Assembly 'Recipe01-09.exe' is valid

However, if the assembly has been modified, you will see this message:

Microsoft (R) .NET Framework Strong Name Utility  Version 2.0.50727.42

Copyright (C) Microsoft Corporation. All rights reserved.



Failed to verify assembly --

Strong name validation failed for assembly 'Recipe01-09.exe'.

Delay-Sign an Assembly

Problem

You need to create a strongly named assembly, but you don't want to give all members of your development team access to the private key component of your strongly named key pair.

Solution

Extract and distribute the public key component of your strongly named key pair. Follow the instructions in recipe 1-9 that describe how to give your assembly a strong name. In addition, specify the /delaysign switch when you compile your assembly. Disable strong name verification for the assembly using the -Vr switch of the Strong Name tool (sn.exe).

Note

If you are using Visual Studio, you can configure your strongly named assembly to be delay-signed by opening the project properties, selecting the Signing tab, and checking the Delay Sign Only box.

How It Works

Assemblies that reference strongly named assemblies contain the public key token of the referenced assemblies. This means the referenced assembly must be strongly named before it can be referenced. In a development environment in which assemblies are regularly rebuilt, this would require every developer and tester to have access to your strongly named key pair—a major security risk.

Instead of distributing the private key component of your strongly named key pair to all members of the development team, the .NET Framework provides a mechanism called delay-signing, with which you can partially strongly name an assembly. The partially strongly named assembly contains the public key and the public key token (required by referencing assemblies) but contains only a placeholder for the signature that would normally be generated using the private key.

After development is complete, the signing authority (who has responsibility for the security and use of your strongly named key pair) re-signs the delay-signed assembly to complete its strong name. The signature is calculated using the private key and embedded in the assembly, making the assembly ready for distribution.

To delay-sign an assembly, you need access only to the public key component of your strongly named key pair. No security risk is associated with distributing the public key, and the signing authority should make the public key freely available to all developers. To extract the public key component from a strongly named key file named MyKeyFile.snk and write it to a file named MyPublicKey.snk, use the command sn -p MyKeyFile.snk MyPublicKey.snk. If you store your strongly named key pair in a CSP key container named MyKeys, extract the public key to a file named MyPublicKey.snk using the command sn -pc MyKeys MyPublicKey.snk.

Once you have a key file containing the public key, you build the delay-signed assembly using the command-line C# compiler by specifying the /delaysign compiler switch. For example, to build a delay-signed assembly from a source file named Recipe01-11, use this command:

csc /delaysign /keyfile:MyPublicKey.snk Recipe01-11.cs

When the runtime tries to load a delay-signed assembly, the runtime will identify the assembly as strongly named and will attempt to verify the assembly, as discussed in recipe 1-10. Because it doesn't have a digital signature, you must configure the runtime on the local machine to stop verifying the assembly's strong name using the command sn -Vr Recipe01-11.exe. Note that you need to do so on every machine on which you want to run your application.

Tip

When using delay-signed assemblies, it's often useful to be able to compare different builds of the same assembly to ensure they differ only by their signatures. This is possible only if a delay-signed assembly has been re-signed using the -R switch of the Strong Name tool. To compare the two assemblies, use the command sn -D assembly1 assembly2.

Once development is complete, you need to re-sign the assembly to complete the assembly's strong name. The Strong Name tool allows you to do this without changing your source code or recompiling the assembly; however, you must have access to the private key component of the strongly named key pair. To re-sign an assembly named Recipe01-11.exe with a key pair contained in the file MyKeys.snk, use the command sn -R Recipe01-11.exe MyKeys.snk. If the keys are stored in a CSP key container named MyKeys, use the command sn -Rc Recipe01-11.exe MyKeys.

Once you have re-signed the assembly, you should turn strong name verification for that assembly back on using the -Vu switch of the Strong Name tool, as in sn -Vu Recipe01-11.exe. To enable verification for all assemblies for which you have disabled strong name verification, use the command sn -Vx. You can list the assemblies for which verification is disabled using the command sn -Vl.

Note

If you are using the .NET Framework 1.0 or 1.1, the command-line C# compiler does not support the /delaysign compiler switch. Instead, you must use the System.Reflection.AssemblyDelaySignAttribute assembly-level attributes within your code to specify that you want the assembly delay-signed. Alternatively, use the Assembly Linker tool (al.exe), which does support the /delaysign switch. Refer to the Assembly Linker information in the .NET Framework SDK documentation for more details.

Sign an Assembly with an Authenticode Digital Signature

Problem

You need to sign an assembly with Authenticode so that users of the assembly can be certain you are its publisher and the assembly is unchanged after signing.

Solution

Use the Sign Tool (signtool.exe) to sign the assembly with your software publisher certificate (SPC).

How It Works

Strong names provide a unique identity for an assembly as well as proof of the assembly's integrity, but they provide no proof as to the publisher of the assembly. The .NET Framework allows you to use Authenticode technology to sign your assemblies. This enables consumers of your assemblies to confirm that you are the publisher, as well as confirm the integrity of the assembly. Authenticode signatures also act as evidence for the signed assembly, which people can use when configuring code access security policy.

To sign your assembly with an Authenticode signature, you need an SPC issued by a recognized certificate authority (CA). A CA is a company entrusted to issue SPCs (along with many other types of certificates) for use by individuals or companies. Before issuing a certificate, the CA is responsible for confirming that the requesters are who they claim to be and also for making sure the requestors sign contracts to ensure they don't misuse the certificates that the CA issues them.

To obtain an SPC, you should view the Microsoft Root Certificate Program Members list at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/rootcertprog.asp. Here you will find a list of CAs, many of whom can issue you an SPC. For testing purposes, you can create a test certificate using the process described in recipe 1-13. However, you can't distribute your software signed with this test certificate. Because a test SPC isn't issued by a trusted CA, most responsible users won't trust assemblies signed with it.

Some CAs will issue your SPC as a Personal Information Exchange file, which has a .pfx suffix—this is the file format that is needed to sign an assembly. Some CAs, however, will issue you with two files (either a private key file (.pvk) and an SPC (.spc) file or a private key file and a certificate file (with a .cer suffix)—if this is the case, you will have to convert your files to the PFX format—see the following usage details for instructions.

Once you have a PFX file, you use the Sign Tool to Authenticode-sign your assembly. The Sign Tool creates a digital signature of the assembly using the private key component of your SPC and embeds the signature and the public part of your SPC in your assembly (including your public key). When verifying your assembly, the consumer decrypts the encrypted hash code using your public key, recalculates the hash of the assembly, and compares the two hash codes to ensure they are the same. As long as the two hash codes match, the consumer can be certain that you signed the assembly and that it has not changed since you signed it.

Usage

If your CA has not issued you with a Personal Information Exchange (PFX) file, then the first step is to convert your certificate to the correct format using the pnk2pfx.exe tool. If you have received a PVK file and a CER file, then you should type the following at the command prompt:

pvk2pfx -pvk MyPrivateKey.pvk -spc MyCertificate.cer -pfx MyCertificate.pfx

If you have received a PVK file and an SPC file, then type the following command:

pvk2pfx -pvk MyPrivateKey.pvk -spc MyCertificate.spc -pfx MyCertificate.pfx

Both of these commands create the MyCertificate.pfx file. To sign an assembly, you type the following command at the command prompt:

signtool sign –f MyCertificate.pfx MyAssembly.exe

Create and Trust a Test Software Publisher Certificate

Problem

You need to create an SPC to allow you to test the Authenticode signing of an assembly.

Solution

Use the Certificate Creation tool (makecert.exe) to create a test X.509 certificate and the pvk2pfx.exe tool to generate a PFX file from this X.509 certificate. Trust the root test certificate using the Set Registry tool (setreg.exe).

How It Works

To create a test SPC for a software publisher named Allen Jones, create an X.509 certificate using the Certificate Creation tool. The command makecert -n "CN=Allen Jones" -sk MyKeys TestCertificate.cer creates a file named TestCertificate.cer containing an X.509 certificate and stores the associated private key in a CSP key container named MyKeys (which is automatically created if it does not exist). Alternatively, you can write the private key to a file by substituting the -sk switch with -sv. For example, to write the private key to a file named PrivateKeys.pvk, use the command makecert -n " CN=Allen Jones" -sv PrivateKey.pvk TestCertificate.cer. If you write your private key to a file, the Certificate Creation tool will prompt you to provide a password with which to protect the private key file (see Figure 1-2).

The Certificate Creation tool requests a password when creating file-based private keys.

Figure 1.2. The Certificate Creation tool requests a password when creating file-based private keys.

The Certificate Creation tool supports many arguments, and Table 1-2 lists some of the more useful ones. You should consult the .NET Framework SDK documentation for full coverage of the Certificate Creation tool.

Table 1.2. Commonly Used Switches of the Certificate Creation Tool

Switch

Description

-e

Specifies the date when the certificate becomes invalid.

-m

Specifies the duration in months that the certificate remains valid.

-n

Specifies an X.500 name to associate with the certificate. This is the name of the software publisher that people will see when they view details of the SPC you create.

-sk

Specifies the name of the CSP key store in which to store the private key.

-ss

Specifies the name of the certificate store where the Certificate Creation tool should store the generated X.509 certificate.

-sv

Specifies the name of the file in which to store the private key.

Once you have created your X.509 certificate with the Certificate Creation tool, you need to convert it to a PFX file with the pvk2pfx.exe tool—this copies the public and private key information contained in the PVK and CER files into a PFX file. To convert the certificate TestCertificate.cer to a PFX file, use the following command:

pvk2pfx -pvk PrivateKey.pvk -spc TestCertificate.cer -pfx TestCertificate.pfx

The final step before you can use your test SPC is to trust the root test CA, which is the default issuer of the test certificate. The Set Registry tool (setreg.exe) makes this a simple task with the command setreg 1 true. When you have finished using your test SPC, you must remove trust of the root test CA using the command setreg 1 false. You can now Authenticode sign assemblies with your test SPC using the process described in recipe 1-12.

Manage the Global Assembly Cache

Problem

You need to add or remove assemblies from the GAC.

Solution

Use the Global Assembly Cache tool (gacutil.exe) from the command line to view the contents of the GAC as well as to add and remove assemblies.

How It Works

Before you can install an assembly in the GAC, the assembly must have a strong name; see recipe 1-9 for details on how to strongly name your assemblies. To install an assembly named SomeAssembly.dll into the GAC, use the command gacutil /i SomeAssembly.dll. You can install different versions of the same assembly in the GAC side by side to meet the versioning requirements of different applications.

To uninstall the SomeAssembly.dll assembly from the GAC, use the command gacutil /u SomeAssembly. Notice that you don't use the .dll extension to refer to the assembly once it's installed in the GAC. This will uninstall all assemblies with the specified name. To uninstall a particular version, specify the version along with the assembly name; for example, use gacutil /u SomeAssembly,Version=1.0.0.5.

To view the assemblies installed in the GAC, use the command gacutil /l. This will produce a long list of all the assemblies installed in the GAC, as well as a list of assemblies that have been precompiled to binary form and installed in the ngen cache. To avoid searching through this list to determine whether a particular assembly is installed in the GAC, use the command gacutil /l SomeAssembly.

Note

The .NET Framework uses the GAC only at runtime; the C# compiler won't look in the GAC to resolve any external references that your assembly references. During development, the C# compiler must be able to access a local copy of any referenced shared assemblies. You can either copy the shared assembly to the same directory as your source code or use the /lib switch of the C# compiler to specify the directory where the compiler can find the required assemblies.

Prevent People from Decompiling Your Code

Problem

You want to ensure people can't decompile your .NET assemblies.

Solution

Build server-based solutions where possible so that people don't have access to your assemblies. If you must distribute assemblies, you have no way to stop people from decompiling them. The best you can do is use obfuscation and components compiled to native code to make your assemblies more difficult to decompile.

How It Works

Because .NET assemblies consist of a standardized, platform-independent set of instruction codes and metadata that describes the types contained in the assembly, they are relatively easy to decompile. This allows decompilers to generate source code that is close to your original code with ease, which can be problematic if your code contains proprietary information or algorithms that you want to keep secret.

The only way to ensure that people can't decompile your assemblies is to stop people from getting your assemblies in the first place. Where possible, implement server-based solutions such as Microsoft ASP.NET applications and web services. With the security correctly configured on your server, nobody will be able to access your assemblies, and therefore they won't be able to decompile them.

When building a server solution is not appropriate, you have the following two options:

  • Use an obfuscator to make it difficult to understand your code once it is decompiled. Some versions of Visual Studio include the Community Edition of an obfuscator named Dotfuscator, which can be started by selecting Dotfuscator Software Services from the Visual Studio 2010 Tools menu. Obfuscators use a variety of techniques to make your assembly difficult to decompile; principal among these techniques are

    • Renaming of private methods and fields in such a way that it's difficult to read and understand the purpose of your code

    • Inserting control flow statements to make the logic of your application difficult to follow

  • Build the parts of your application that you want to keep secret in native DLLs or COM objects, and then call them from your managed application using P/Invoke or COM Interop. (See Chapter 12 for recipes that show you how to call unmanaged code.)

Neither approach will stop a skilled and determined person from reverse engineering your code, but both approaches will make the job significantly more difficult and deter most casual observers.

Note

The risks of application decompilation aren't specific to C# or .NET. A determined person can reverse engineer any software if they have the time and the skill.

Manipulate the Appearance of the Console

Problem

You want to control the visual appearance of the Windows console.

Solution

Use the static properties and methods of the System.Console class.

How It Works

The .NET Framework gives you a high degree of control over the appearance and operation of the Windows console. Table 1-3 describes the properties and methods of the Console class that you can use to control the console's appearance.

Table 1.3. Properties and Methods to Control the Appearance of the Console

Member

Description

Properties

 

BackgroundColor

Gets and sets the background color of the console using one of the values from the System.ConsoleColor enumeration. Only new text written to the console will appear in this color. To make the entire console this color, call the method Clear after you have configured the BackgroundColor property.

BufferHeight

Gets and sets the buffer height in terms of rows.

BufferWidth

Gets and sets the buffer width in terms of columns.

CursorLeft

Gets and sets the column position of the cursor within the buffer.

CursorSize

Gets and sets the height of the cursor as a percentage of a character cell.

CursorTop

Gets and sets the row position of the cursor within the buffer.

CursorVisible

Gets and sets whether the cursor is visible.

ForegroundColor

Gets and sets the text color of the console using one of the values from the System.ConsoleColor enumeration. Only new text written to the console will appear in this color. To make the entire console this color, call the method Clear after you have configured the ForegroundColor property.

LargestWindowHeight

Returns the largest possible number of rows based on the current font and screen resolution.

LargestWindowWidth

Returns the largest possible number of columns based on the current font and screen resolution.

Title

Gets and sets text shown in the title bar.

WindowHeight

Gets and sets the width in terms of character rows.

WindowWidth

Gets and sets the width in terms of character columns.

Methods

 

Clear

Clears the console.

ResetColor

Sets the foreground and background colors to their default values as configured within Windows.

SetWindowSize

Sets the width and height in terms of columns and rows.

The Code

The following example demonstrates how to use the properties and methods of the Console class to dynamically change the appearance of the Windows console:

using System;

namespace Apress.VisualCSharpRecipes.Chapter01
{
    public class Recipe01_16
    {
        static void Main(string[] args)
        {
            // Display the standard console.
            Console.Title = "Standard Console";
            Console.WriteLine("Press Enter to change the console's appearance.");
            Console.ReadLine();

            // Change the console appearance and redisplay.
            Console.Title = "Colored Text";
            Console.ForegroundColor = ConsoleColor.Red;
            Console.BackgroundColor = ConsoleColor.Green;
            Console.WriteLine("Press Enter to change the console's appearance.");
            Console.ReadLine();

            // Change the console appearance and redisplay.
            Console.Title = "Cleared / Colored Console";
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.BackgroundColor = ConsoleColor.Yellow;
            Console.Clear();
            Console.WriteLine("Press Enter to change the console's appearance.");
            Console.ReadLine();
// Change the console appearance and redisplay.
            Console.Title = "Resized Console";
            Console.ResetColor();
            Console.Clear();
            Console.SetWindowSize(100, 50);
            Console.BufferHeight = 500;
            Console.BufferWidth = 100;
            Console.CursorLeft = 20;
            Console.CursorSize = 50;
            Console.CursorTop = 20;
            Console.CursorVisible = false;
            Console.WriteLine("Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Create a Static Class

Problem

You need to create a class that contains only static members.

Solution

Add the static keyword to your class declaration.

How It Works

A static class can contain only static members. You cannot instantiate the class using a constructor and you must refer to the members through the class name. The compiler will warn you if you add an instance member in the class (i.e., one that does not have the static keyword in its declaration)—you will still be able to use the static class, but the non-static methods will be inaccessible.

The Code

The following example defines a static class and calls the method and property it contains:

using System;

namespace Apress.VisualCSharpRecipes.Chapter01
{
    public static class MyStaticClass
    {
public static string getMessage()
        {
            return "This is a static member";
        }

        public static string StaticProperty
        {
            get;
            set;
        }

    }

    public class Recipe01_16
    {
        static void Main(string[] args)
        {
            // Call the static method and print out the result.
            Console.WriteLine(MyStaticClass.getMessage());

            // Set and get the property.
            MyStaticClass.StaticProperty = "this is the property value";
            Console.WriteLine(MyStaticClass.StaticProperty);

            Console.WriteLine("Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Create an Anonymous Type

Problem

You need to use an anonymous type for short-term data manipulation.

Solution

Declare a variable using the special type var, and then define the type's content using the new keyword.

How It Works

Anonymous types are convenient C# features that allow you to encapsulate a set of properties into a single object without having to define a type beforehand. The types of the properties you define in an anonymous type are inferred by the compiler and are read-only. The following fragment creates an anonymous type with two properties—a string and an int.

var anon = new {
    Name = "Adam Freeman",
    Age = 37;
};

The values of the properties can be accessed by calling anon.Name and anon.Age.

Anonymous types have some limitations. The properties are read-only, only properties (and not methods) can be defined, and their scope is limited to the method in which they are created—they cannot be passed as arguments to other methods. Anonymous types are often used in conjunction with LINQ—see Chapter 16 for LINQ recipes.

The Code

The following example creates an anonymous type that itself contains a nested anonymous type, and then prints out the various fields:

using System;

namespace Apress.VisualCSharpRecipes.Chapter01
{

    public class Recipe01_18
    {
        static void Main(string[] args)
        {

            // Create an anoymous type.
            var joe = new {
                Name = "Joe Smith",
                Age  = 42,
                Family = new {
                    Father = "Pa Smith",
                    Mother = "Ma Smith",
                    Brother = "Pete Smith"
                },
            };

            // Access the members of the anonymous type.
            Console.WriteLine("Name: {0}", joe.Name);
            Console.WriteLine("Age: {0}", joe.Age);
            Console.WriteLine("Father: {0}", joe.Family.Father);
            Console.WriteLine("Mother: {0}", joe.Family.Mother);
            Console.WriteLine("Brother: {0}", joe.Family.Brother);

            Console.WriteLine("Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Running the example gives the following results:

Name: Joe Smith

Age: 42

Father: Pa Smith

Mother: Ma Smith

Brother: Pete Smith

Main method complete. Press Enter.

Create an ExpandoObject Dynamic Type

Problem

You need to create a type to contain a set of read-write properties.

Solution

Use the System.Dynamic.ExpandoObject class.

How It Works

In the previous recipe, we showed you how to encapsulate a set of properties using an anonymous type. One of the limitations of anonymous types is that the encapsulated properties are read-only. You can achieve a similar effect, but with properties that can be modified, using the System.Dynamic.ExpandoObject class.

The ExpandoObject is part of the dynamic language support introduced in.NET 4.0. Chapter 13 includes a recipe for using dynamic types more generally, but much of the functionality that the dynamic language support provides is not intended for use in C#. The most important thing to remember about dynamic types is that the calls you make to them in your code are not checked by the compiler, and you will not be able to see the impact of any misspelled method, parameter, property, or type name until your code is executed.

The ExpandoObject is useful because you can dynamically add properties just by assigning values to them. For example, the following fragment creates a new instance of ExpandoObject and creates a new property called Name with a value of Joe Smith.

dynamic expando = new ExpandoObject();
expando.Name = "Joe Smith";

We can get or set the value of the Name property as for any other type. Note that we have declared the instance of ExpandoObject using the dynamic keyword—if you declare the variable as an instance of ExpandoObject (by calling ExpandoObject expando = new ExpandoObject(), for example), then you will not be able to add any properties. You can define properties of any type, including other instances of ExpandoObject—see the example code for this recipe for an illustration of this.

The Code

The following example creates an ExpandoObject and writes out the property values:

using System;
using System.Dynamic;

namespace Apress.VisualCSharpRecipes.Chapter01
{
    public class Recipe01_19
    {
        static void Main(string[] args)
        {
            // Create the expando.
            dynamic expando = new ExpandoObject();
            expando.Name = "Joe Smith";
            expando.Age = 42;
            expando.Family = new ExpandoObject();
            expando.Family.Father = "Pa Smith";
            expando.Family.Mother = "Ma Smith";
            expando.Family.Brother = "Pete Smith";

            // Access the members of the dynamic type.
            Console.WriteLine("Name: {0}", expando.Name);
            Console.WriteLine("Age: {0}", expando.Age);
            Console.WriteLine("Father: {0}", expando.Family.Father);
            Console.WriteLine("Mother: {0}", expando.Family.Mother);
            Console.WriteLine("Brother: {0}", expando.Family.Brother);

            // Change a value.
            expando.Age = 44;
            // Add a new property.
            expando.Family.Sister = "Kathy Smith";

            Console.WriteLine("
Modified Values");
            Console.WriteLine("Age: {0}", expando.Age);
            Console.WriteLine("Sister: {0}", expando.Family.Sister);

            Console.WriteLine("Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Define an Automatically Implemented Property

Problem

You want to declare a property that requires no logic in its accessors.

Solution

Use an automatically implemented property and allow the runtime to manage the underlying variable on your behalf.

How It Works

Many properties are defined simply to hold values and require no logic to process values in the accessors—for these cases, the standard pattern of defining a backing variable in the parent class and mapping access to it via the accessors is inelegant. For such properties, you can use the automatic implementation feature, where the compiler creates the backing instance and you do not need to provide any code in the accessors at all. The following fragment demonstrates an auto-implemented string property:

public string MyAutoImplementedProperty
{
   get;
   set;
}

The compile will generate an error if you do not specify both the get and set accessors, but otherwise automatically implemented properties can be used in just the same way as the regular kind.

The Code

The following example defines two static, automatically implemented properties and prints out the default values of each of them. Values are then assigned and read back out again.

using System;

namespace Apress.VisualCSharpRecipes.Chapter01
{

    public class Recipe01_20
    {
        // Define a static string property.
        static string MyStaticProperty
        {
            get;
            set;
        }


        static int MyStaticIntProperty
        {
            get;
            set;
        }

        static void Main(string[] args)
        {
            // Write out the default values.
            Console.WriteLine("Default property values");
            Console.WriteLine("Default string property value: {0}", MyStaticProperty);
            Console.WriteLine("Default int property value: {0}", MyStaticIntProperty);

            // Set the property values.
            MyStaticProperty = "Hello, World";
            MyStaticIntProperty = 32;

            // Write out the changed values.
            Console.WriteLine("
Property values");
            Console.WriteLine("String property value: {0}", MyStaticProperty);
            Console.WriteLine("Int property value: {0}", MyStaticIntProperty);

            Console.WriteLine("
Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Running the program gives the following result:

Default property values

Default string property value:

Default int property value: 0



Property values

String property value: Hello, World

Int property value: 32



Main method complete. Press Enter.

Overload an Operator

Problem

You want to be able to use your types with the standard operators (+, −, *, etc.).

Solution

Overload one or more operators by defining static methods with the operator symbol as the method name and using the operator keyword in the method declaration.

How It Works

To implement operators in your classes, you simply define static methods to overload the operator you want to use—for example, the following fragment shows the declaration of a method that implements the addition operator (+) to be used when adding together two instances of the type Word:

public static string operator +(Word w1, Word w2)

Adding and implementing this method to the Word class allows us to define what happens when we use the addition operator on two instances of the Word type:

string result = word1 + word2;

Notice that the result of our addition is a string—you can return any type you choose. You can also define the behavior for when operators are applied on different types, such as the following, which declares a method that overrides the operator for when an instance of Word and an int are added together:

public static Word operator +(Word w, int i)

The following fragment allows us to use the operator like this:

Word newword = word1 + 7;

Note that the order of the arguments is important—the previous fragment defines the behavior for a Word + int operation, but not int + Word (i.e., the same types, but with their order reversed). We would need to define another method to support both orderings.

See the code for this recipe for an example of how to use these operators and implementations for the operator overloads we have referred to. You can override the following operators:

+, −, *, /, %, &, |, ^, <<, >>

The Code

The following example defines the type Word, which has two overridden addition operators: one that inserts a space in between words when they are added together, and another for adding an int value to a word.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Dynamic;

namespace Apress.VisualCSharpRecipes.Chapter01
{
    public class Word
    {
        public string Text
        {
            get;
            set;
        }

        public static string operator +(Word w1, Word w2)
        {
            return w1.Text + " " + w2.Text;
        }

        public static Word operator +(Word w, int i)
        {
return new Word() { Text = w.Text + i.ToString()};
        }

        public override string ToString()
        {
            return Text;
        }
    }

    public class Recipe01_21
    {
        static void Main(string[] args)
        {

            // Create two word instances.
            Word word1 = new Word() { Text = "Hello" };
            Word word2 = new Word() { Text = "World" };

            // Print out the values.
            Console.WriteLine("Word1: {0}", word1);
            Console.WriteLine("Word2: {0}", word2);
            Console.WriteLine("Added together: {0}", word1 + word2);
            Console.WriteLine("Added with int: {0}", word1 + 7);

            Console.WriteLine("
Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Running the example produces the following results:

Word1: Hello

Word2: World

Added together: Hello World

Added with int: Hello7



Main method complete. Press Enter.

Define a Conversion Operator

Problem

You need to be able to convert from one type to another.

Solution

Implement implicit or explicit conversion operator methods.

How It Works

You can specify how your type is converted to other types and, equally, how other types are converted to your type, by declaring conversion operators in your class. A conversion operator is a static method that is named for the type that you wish to convert to and that has the type you wish to convert from. For example, the following method fragment is a conversion operator from the Word type (taken from the code for this recipe) that converts an instance of string to an instance of Word:

public static explicit operator Word(string str)
{
   return new Word() { Text = str };
}

Defining this member in the Word class allows us to perform conversions such as the following:

Word word = (Word)"Hello";

Note that we have had to explicitly cast the string to Word—this is because our conversion operator included the explicit keyword. You can enable implicit conversion by using the implicit keyword, such as this:

public static implicit operator Word(string str)
{
   return new Word() { Text = str };
}

With the implicit keyword, now both of the following statements would compile:

Word word = (Word)"Hello";
Word word = "Hello";

Conversion operators must always be static, and you must choose between an explicit and an implicit conversion—you cannot define different conversion operators for the same pair of types but with different keywords.

The Code

The following example defines and demonstrates implicit and explicit conversion operators for the Word type.

using System;

namespace Apress.VisualCSharpRecipes.Chapter01
{
    public class Word
    {
        public string Text
        {
            get;
            set;
        }

        public static explicit operator Word(string str)
        {
            return new Word() { Text = str };
        }

        public static implicit operator string(Word w)
        {
            return w.Text;
        }

        public static explicit operator int(Word w)
        {
            return w.Text.Length;
        }

        public override string ToString()
        {
            return Text;
        }
    }

    public class Recipe01_22
    {
        static void Main(string[] args)
        {

            // Create a word instance.
            Word word1 = new Word() { Text = "Hello"};

            // Implicitly convert the word to a string.
            string str1 = word1;
            // Explicitly convert the word to a string.
            string str2 = (string)word1;
Console.WriteLine("{0} - {1}", str1, str2);

            // Convert a string to a word.
            Word word2 = (Word)"Hello";

            // Convert a word to an int.
            int count = (int)word2;

            Console.WriteLine("Length of {0} = {1}", word2.ToString(), count);

            Console.WriteLine("
Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Handle an Event with an Anonymous Function

Problem

You need to handle an event, but don't want to define a method in your class to do so.

Solution

Use an anonymous delegate or a lambda expression.

How It Works

You can create a delegate for an event handler anonymously, meaning that you do not have to define a separate method for the delegate to call. To add an anonymous delegate, pass the method arguments to the delegate keyword and implement the method body between braces. The following fragment demonstrates how to register for an event using an anonymous delegate:

MyEvent += new EventHandler(delegate(object sender, EventArgs eventargs)
{
   ...implement method body...
});

Anonymous delegates simplify source code where you do not need to share an implementation between handlers and where you do not need to unregister for the event—you cannot unregister because you do not have a reference to unregister with when using anonymous delegates.

You can also handle events using a lambda expression, which is another form of the anonymous function. Lambda expressions use the lambda operator, =>, which is read as "goes to." On the left of the expression, you list names for the variables that will be passed to your expression, and to the right, you write the code that you want to execute, referring to the variables you defined as required. The following fragment shows how to use a lambda expression to handle an event:

MyEvent += new EventHandler((sender, eventargs) =>
{
   ... implment method, referring to sender and eventargs if required...
});

With a lambda expression, the compiler works out what the types of the variables on the left of the "goes to" sign should be, so you need only specify the names that you want to use.

The Code

The following example uses a named method to handle an event and illustrates how to do the same thing using an anonymous delegate and a lambda expression:

using System;

namespace Apress.VisualCSharpRecipes.Chapter01
{
    public class Recipe01_23
    {
        public static EventHandler MyEvent;

        static void Main(string[] args)
        {
            // Use a named method to register for the event.
            MyEvent += new EventHandler(EventHandlerMethod);

            // Use an anonymous delegate to register for the event.
            MyEvent += new EventHandler(delegate(object sender, EventArgs eventargs)
            {
                Console.WriteLine("Anonymous delegate called");
            });

            // Use a lamda expression to register for the event.
            MyEvent += new EventHandler((sender, eventargs) =>
            {
                Console.WriteLine("Lamda expression called");
            });

            Console.WriteLine("Raising the event");
            MyEvent.Invoke(new object(), new EventArgs());

            Console.WriteLine("
Main method complete. Press Enter.");
            Console.ReadLine();
        }
static void EventHandlerMethod(object sender, EventArgs args)
        {
            Console.WriteLine("Named method called");
        }
    }
}

Implement a Custom Indexer

Problem

You need to be able to access data in your custom type like an array.

Solution

Implement a custom indexer.

How It Works

You can enable array-style indexing for you class by implementing a custom indexer. A custom indexer is like a property, but the name of the property is the keyword this. You can choose any type to be used as the index and any type to return as the result—the code for this recipe illustrates a custom indexer that uses a string for the index and returns a custom type.

The Code

The following example demonstrates a class, WeatherForecast, which uses a custom string indexer in order to perform calculations on the fly in order to generate a result:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Apress.VisualCSharpRecipes.Chapter01
{
    public class WeatherReport
    {
        public int DayOfWeek
        {
            get;
            set;
        }
public int DailyTemp
        {
            get;
            set;
        }

        public int AveTempSoFar
        {
            get;
            set;
        }
    }

    public class WeatherForecast
    {
        private int[] temps = { 54, 63, 61, 55, 61, 63, 58 };
        IList<string> daysOfWeek = new List<string>()
            {"Monday", "Tuesday", "Wednesday",
            "Thursday", "Friday", "Saturday", "Sunday"};

        public WeatherReport this[string dow]
        {
            get
            {
                // Get the day of the week index.
                int dayindex = daysOfWeek.IndexOf(dow);
                return new WeatherReport()
                {
                    DayOfWeek = dayindex,
                    DailyTemp = temps[dayindex],
                    AveTempSoFar = calculateTempSoFar(dayindex)
                };
            }
            set
            {
                temps[daysOfWeek.IndexOf(dow)] = value.DailyTemp;
            }
        }

        private int calculateTempSoFar(int dayofweek)
        {
            int[] subset = new int[dayofweek + 1];
            Array.Copy(temps, 0, subset, 0, dayofweek + 1);
            return (int)subset.Average();
        }
    }

    public class Recipe01_24
    {
        static void Main(string[] args)
        {
// Create a new weather forecast.
            WeatherForecast forecast = new WeatherForecast();

            // Use the indexer to obtain forecast values and write them out.
            string[] days = {"Monday", "Thursday", "Tuesday", "Saturday"};
            foreach (string day in days)
            {
                WeatherReport report = forecast[day];
                Console.WriteLine("Day: {0} DayIndex {1}, Temp: {2} Ave {3}", day,
                    report.DayOfWeek, report.DailyTemp, report.AveTempSoFar);
            }

            // Change one of the temperatures.
            forecast["Tuesday"] = new WeatherReport()
            {
                DailyTemp = 34
            };

            // Repeat the loop.
            Console.WriteLine("
Modified results...");
            foreach (string day in days)
            {
                WeatherReport report = forecast[day];
                Console.WriteLine("Day: {0} DayIndex {1}, Temp: {2} Ave {3}", day,
                    report.DayOfWeek, report.DailyTemp, report.AveTempSoFar);
            }

            Console.WriteLine("
Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Running the program gives the following results:

Day: Monday DayIndex 0, Temp: 54 Ave 54

Day: Thursday DayIndex 3, Temp: 55 Ave 58

Day: Tuesday DayIndex 1, Temp: 63 Ave 58

Day: Saturday DayIndex 5, Temp: 63 Ave 59



Modified results...

Day: Monday DayIndex 0, Temp: 54 Ave 54

Day: Thursday DayIndex 3, Temp: 55 Ave 51

Day: Tuesday DayIndex 1, Temp: 34 Ave 44

Day: Saturday DayIndex 5, Temp: 63 Ave 54



Main method complete. Press Enter
..................Content has been hidden....................

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