Chapter 1. Introduction to .NET and C#

Topics in This Chapter

  • Overview of the .NET Framework: Architecture and features.

  • Common Language RuntimeAn overview of the tasks performed by the runtime portion of the .NET Framework: Just-in-Time (JIT) compiler, loading assemblies, and code verification.

  • Common Type System and Common Language SpecificationsRules that govern Common Language Runtime (CLR) compatibility and language interoperability.

  • AssembliesA look at the structure of an assembly, the philosophy behind it, and the difference between private and shared assemblies.

  • Framework Class LibraryThe Framework Library supplies hundreds of base classes grouped into logical namespaces.

  • Development ToolsSeveral tools are provided with .NET to aid code development. These include Ildasm for disassembling code, WinCV to view the properties of a class, and the Framework Configuration tool.

  • Compiling and Running C# ProgramsUsing the C# compiler from the command line and options for structuring an application.

The effective use of a language requires a good deal more than learning the syntax and features of the language. In fact, the greater part of the learning curve for new technology is now concentrated in the programming environment. It is not enough to be proficient with the C# language; the successful developer and software architect must also be cognizant of the underlying class libraries and the tools available to probe these libraries, debug code, and check the efficiency of underlying code.

The purpose of this chapter is to provide an awareness of the .NET environment before you proceed to the syntax and semantics of the C# language. The emphasis is on how the environment, not the language, will affect the way you develop software. If you are new to .NET, it is necessary to embrace some new ideas. .NET changes the way you think of dealing with legacy code and version control; it changes the way program resources are disposed of; it permits code developed in one language to be used by another; it simplifies code deployment by eliminating a reliance on the system registry; and it creates a self-describing metalanguage that can be used to determine program logic at runtime. You will bump into all of these at some stage of the software development process, and they will influence how you design and deploy your applications.

To the programmer's eye, the .NET platform consists of a runtime environment coupled with a base class library. The layout of this chapter reflects that viewpoint. It contains separate sections on the Common Language Runtime (CLR) and the Framework Class Library (FCL). It then presents the basic tools that a developer may use to gain insight into the inner workings of the .NET Framework, as well as manage and distribute applications. As a prelude to Chapter 2, the final section introduces the C# compiler with examples of its use.

Overview of the .NET Framework

The .NET Framework is designed as an integrated environment for seamlessly developing and running applications on the Internet, on the desktop as Windows Forms, and even on mobile devices (with the Compact Framework). Its primary objectives are as follows:

  • To provide a consistent object-oriented environment across the range of applications.

  • To provide an environment that minimizes the versioning conflicts (“DLL Hell”) that has bedeviled Windows (COM) programmers, and to simplify the code distribution/installation process.

  • To provide a portable environment, based on certified standards, that can be hosted by any operating system. Already, C# and a major part of the .NET runtime, the Common Language Infrastructure (CLI), have been standardized by the ECMA.[1]

  • To provide a managed environment in which code is easily verified for safe execution.

To achieve these broad objectives, the .NET Framework designers settled on an architecture that separates the framework into two parts: the Common Language Runtime (CLR) and the Framework Class Library (FCL). Figure 1-1 provides a stylized representation of this.

.NET Framework

Figure 1-1. .NET Framework

The CLR—which is Microsoft's implementation of the CLI standard—handles code execution and all of the tasks associated with it: compilation, memory management, security, thread management, and enforcement of type safety and use. Code that runs under the CLR is referred to as managed code. This is to distinguish it from unmanaged code that does not implement the requirements to run in the CLR—such as COM or Windows API based components.

The other major component, the Framework Class Library, is a reusable code library of types (classes, structures, and so on) available to applications running under .NET. As the figure shows, these include classes for database access, graphics, interoperating with unmanaged code, security, and both Web and Windows forms. All languages that target the .NET Framework use this common class library. Thus, after you gain experience using these types, you can apply that knowledge to any .NET language you may choose to program in.

Microsoft .NET and the CLI Standards

A natural concern for a developer that chooses to invest the time in learning C# and .NET is whether the acquired skill set can be transferred to other platforms. Specifically, is .NET a Microsoft product tethered only to the Windows operating system? Or is it a portable runtime and development platform that will be implemented on multiple operating systems? To answer the question, it is necessary to understand the relationship among Microsoft .NET, C#, and the Common Language Infrastructure (CLI) standards.

The CLI defines a platform-independent virtual code execution environment. It specifies no operating system, so it could just as easily be Linux as Windows. The centerpiece of the standard is the definition for a Common Intermediate Language (CIL) that must be produced by CLI compliant compilers and a type system that defines the data types supported by any compliant language. As described in the next section, this intermediate code is compiled into the native language of its host operating system.

The CLI also includes the standards for the C# language, which was developed and promoted by Microsoft. As such, it is the de facto standard language for .NET. However, other vendors have quickly adopted the CIL standard and produced—just to name a few—Python, Pascal, Fortran, Cobol, and Eiffel .NET compilers.

The .NET Framework, as depicted in Figure 1-1, is Microsoft's implementation of the CLI standards. The most important thing to note about this implementation is that it contains a great deal more features than are specified by the CLI architecture. To illustrate this, compare it to the CLI standards architecture shown in Figure 1-2.

Architecture defined by CLI specifications

Figure 1-2. Architecture defined by CLI specifications

Briefly, the CLI defines two implementations: a minimal implementation known as a Kernel Profile and a more feature rich Compact Profile. The kernel contains the types and classes required by a compiler that is CLI compliant. The Base Class Library holds the basic data type classes, as well as classes that provide simple file access, define security attributes, and implement one-dimensional arrays. The Compact Profile adds three class libraries: an XML library that defines simple XML parsing, a Network library that provides HTTP support and access to ports, and a Reflection library that supports reflection (a way for a program to examine itself through metacode).

This book, which describes the Microsoft implementation, would be considerably shorter if it described only the CLI recommendations. There would be no chapters on ADO.NET (database classes), ASP.NET (Web classes), or Windows Forms—and the XML chapters would be greatly reduced. As you may guess, these libraries depend on the underlying Windows API for functionality. In addition, .NET permits a program to invoke the Win32 API using an Interop feature. This means that a .NET developer has access not only to the Win32 API but also legacy applications and components (COM).

By keeping this rather wide bridge to Windows, Microsoft's .NET implementation becomes more of a transparent than virtual environment—not that there's anything wrong with that. It gives developers making the transition to .NET the ability to create hybrid applications that combine .NET components with preexisting code. It also means that the .NET implementation code is not going to be ported to another operating system.

The good news for developers—and readers of this book—is that the additional Microsoft features are being adopted by CLI open source initiatives. Mono[2] , one of the leading CLI projects, already includes major features such as ADO.NET, Windows Forms, full XML classes, and a rich set of Collections classes. This is particularly significant because it means the knowledge and skills obtained working with Microsoft .NET can be applied to implementations on Linux, BSD, and Solaris platforms. With that in mind, let's take an overview of the Microsoft CLI implementation.

Common Language Runtime

The Common Language Runtime manages the entire life cycle of an application: it locates code, compiles it, loads associated classes, manages its execution, and ensures automatic memory management. Moreover, it supports cross-language integration to permit code generated by different languages to interact seamlessly. This section peers into the inner workings of the Common Language Runtime to see how it accomplishes this. It is not an in-depth discussion, but is intended to make you comfortable with the terminology, appreciate the language-neutral architecture, and understand what's actually happening when you create and execute a program.

Compiling .NET Code

Compilers that are compliant with the CLR generate code that is targeted for the runtime, as opposed to a specific CPU. This code, known variously as Common Intermediate Language (CIL), Intermediate Language (IL), or Microsoft Intermediate Language (MSIL), is an assembler-type language that is packaged in an EXE or DLL file. Note that these are not standard executable files and require that the runtime's Just-in-Time (JIT) compiler convert the IL in them to a machine-specific code when an application actually runs. Because the Common Language Runtime is responsible for managing this IL, the code is known as managed code.

This intermediate code is one of the keys to meeting the .NET Framework's formal objective of language compatibility. As Figure 1-3 illustrates, the Common Language Runtime neither knows—nor needs to know—which language an application is created in. Its interaction is with the language-independent IL. Because applications communicate through their IL, output from one compiler can be integrated with code produced by a different compiler.

Common Language Runtime functions

Figure 1-3. Common Language Runtime functions

Another .NET goal, platform portability, is addressed by localizing the creation of machine code in the JIT compiler. This means that IL produced on one platform can be run on any other platform that has its own framework and a JIT compiler that emits its own machine code.

In addition to producing IL, compilers that target the CLR must emit metadata into every code module. The metadata is a set of tables that allows each code module to be self-descriptive. The tables contain information about the assembly containing the code, as well as a full description of the code itself. This information includes what types are available, the name of each type, type members, the scope or visibility of the type, and any other type features. Metadata has many uses:

  • The most important use is by the JIT compiler, which gathers all the type information it needs for compiling directly from the metacode. It also uses this information for code verification to ensure the program performs correct operations. For example, the JIT ensures that a method is called correctly by comparing the calling parameters with those defined in the method's metadata.

  • Metadata is used in the Garbage Collection process (memory management). The garbage collector (GC) uses metadata to know when fields within an object refer to other objects so that the GC can determine what objects can and can't have their memory reclaimed.

  • .NET provides a set of classes that provide the functionality to read metadata from within a program. This functionality is known collectively as reflection. It is a powerful feature that permits a program to query the code at runtime and make decisions based on its discovery. As we will see later in the book, it is the key to working with custom attributes, which are a C#-supported construct for adding custom metadata to a program.

IL and metadata are crucial to providing language interoperability, but its real-world success hinges on all .NET compilers supporting a common set of data types and language specifications. For example, two languages cannot be compatible at the IL level if one language supports a 32-bit signed integer and the other does not. They may differ syntactically (for example, C# int versus a Visual Basic Integer), but there must be agreement of what base types each will support.

As discussed earlier, the CLI defines a formal specification, called the Common Type System (CTS), which is an integral part of the Common Language Runtime. It describes how types are defined and how they must behave in order to be supported by the Common Language Runtime.

Common Type System

The CTS provides a base set of data types for each language that runs on the .NET platform. In addition, it specifies how to declare and create custom types, and how to manage the lifetime of instances of these types. Figure 1-4 shows how .NET organizes the Common Type System.

Base types defined by Common Type System

Figure 1-4. Base types defined by Common Type System

Two things stand out in this figure. The most obvious is that types are categorized as reference or value types. This taxonomy is based on how the types are stored and accessed in memory: reference types are accessed in a special memory area (called a heap) via pointers, whereas value types are referenced directly in a program stack. The other thing to note is that all types, both custom and .NET defined, must inherit from the predefined System.Object type. This ensures that all types support a basic set of inherited methods and properties.

Core Note

Core Note

In .NET, “type” is a generic term that refers to a class, structure, enumeration, delegate, or interface.

A compiler that is compliant with the CTS specifications is guaranteed that its types can be hosted by the Common Language Runtime. This alone does not guarantee that the language can communicate with other languages. There is a more restrictive set of specifications, appropriately called the Common Language Specification (CLS), that provides the ultimate rules for language interoperability. These specifications define the minimal features that a compiler must include in order to target the CLR.

Table 1-1 contains some of the CLS rules to give you a flavor of the types of features that must be considered when creating CLS-compliant types (a complete list is included with the .NET SDK documentation).

Table 1-1. Selected Common Language Specification Features and Rules

Feature

Rule

Visibility (Scope)

The rules apply only to those members of a type that are available outside the defining assembly.

Characters and casing

For two variables to be considered distinct, they must differ by more than just their case.

Primitive types

The following primitive data types are CLS compliant: Byte, Int16, Int32, Int64, Single, Double, Boolean, Char, Decimal, IntPtr, and String.

Constructor invocation

A constructor must call the base class's constructor before it can access any of its instance data.

Array bounds

All dimensions of arrays must have a lower bound of zero (0).

Enumerations

The underlying type of an enumeration (enum) must be of the type Byte, Int16, Int32, or Int64.

Method signature

All return and parameter types used in a type or member signature must be CLS compliant.

These rules are both straightforward and specific. Let's look at a segment of C# code to see how they are applied:

public class Conversion
{
   public double Metric( double inches)
   { return (2.54 * inches); }
   public double metric( double miles)
   { return (miles / 0.62); }
}

Even if you are unfamiliar with C# code, you should still be able to detect where the code fails to comply with the CLS rules. The second rule in the table dictates that different names must differ by more than case. Obviously, Metric fails to meet this rule. This code runs fine in C#, but a program written in Visual Basic.NET—which ignores case sensitivity—would be unable to distinguish between the upper and lowercase references.

Assemblies

All of the managed code that runs in .NET must be contained in an assembly. Logically, the assembly is referenced as one EXE or DLL file. Physically, it may consist of a collection of one or more files that contain code or resources such as images or XML data.

An assembly is created when a .NET compatible compiler converts a file containing source code into a DLL or EXE file. As shown in Figure 1-5, an assembly contains a manifest, metadata, and the compiler-generated Intermediate Language (IL). Let's take a closer look at these:

  • ManifestEach assembly must have one file that contains a manifest. The manifest is a set of tables containing metadata that lists the names of all files in the assembly, references to external assemblies, and information such as name and version that identify the assembly. Strongly named assemblies (discussed later) also include a unique digital signature. When an assembly is loaded, the CLR's first order of business is to open the file containing the manifest so it can identify the members of the assembly.

  • MetadataIn addition to the manifest tables just described, the C# compiler produces definition and reference tables. The definition tables provide a complete description of the types contained in the IL. For instance, there are tables defining types, methods, fields, parameters, and properties. The reference tables contain information on all references to types and other assemblies. The JIT compiler relies on these tables to convert the IL to native machine code.

  • ILThe role of Intermediate Language has already been discussed. Before the CLR can use IL, it must be packaged in an EXE or DLL assembly. The two are not identical: an EXE assembly must have an entry point that makes it executable; a DLL, on the other hand, is designed to function as a code library holding type definitions.

Single file assembly

Figure 1-5. Single file assembly

The assembly is more than just a logical way to package executable code. It forms the very heart of the .NET model for code deployment, version control, and security:

  • All managed code, whether it is a stand-alone program, a control, or a DLL library containing reusable types, is packaged in an assembly. It is the most atomic unit that can be deployed on a system. When an application begins, only those assemblies required for initialization must be present. Other assemblies are loaded on demand. A judicious developer can take advantage of this to partition an application into assemblies based on their frequency of use.

  • In .NET jargon, an assembly forms a version boundary. The version field in the manifest applies to all types and resources in the assembly. Thus, all the files comprising the assembly are treated as a single unit with the same version. By decoupling the physical package from the logical, .NET can share a logical attribute among several physical files. This is the fundamental characteristic that separates an assembly from a system based on the traditional DLLs.

  • An assembly also forms a security boundary on which access permissions are based. C# uses access modifiers to control how types and type members in an assembly can be accessed. Two of these use the assembly as a boundary: public permits unrestricted access from any assembly; internal restricts access to types and members within the assembly.

As mentioned, an assembly may contain multiple files. These files are not restricted to code modules, but may be resource files such as graphic images and text files. A common use of these files is to permit resources that enable an application to provide a screen interface tailored to the country or language of the user. There is no limit to the number of files in the assembly. Figure 1-6 illustrates the layout of a multi-file assembly.

Multi-file assembly

Figure 1-6. Multi-file assembly

In the multi-file assembly diagram, notice that the assembly's manifest contains the information that identifies all files in the assembly.

Although most assemblies consist of a single file, there are several cases where multi-file assemblies are advantageous:

  • They allow you to combine modules created in different programming languages. A programming shop may rely on Visual Basic.NET for its Rapid Application Development (RAD) and C# for component or enterprise development. Code from both can coexist and interact in the .NET assembly.

  • Code modules can be partitioned to optimize how code is loaded into the CLR. Related and frequently used code should be placed in one module; infrequently used code in another. The CLR does not load the modules until they are needed. If creating a class library, go a step further and group components with common life cycle, version, and security needs into separate assemblies.

  • Resource files can be placed in their own module separate from IL modules. This makes it easier for multiple applications to share common resources.

Multi-file assemblies can be created by executing the C# compiler from the command line or using the Assembly Linker utility, Al.exe. An example using the C# compiler is provided in the last section of this chapter. Notably, Visual Studio.NET 2005 does not support the creation of multi-file assemblies.

Private and Shared Assemblies

Assemblies may be deployed in two ways: privately or globally. Assemblies that are located in an application's base directory or a subdirectory are called privately deployed assemblies. The installation and updating of a private assembly could not be simpler. It only requires copying the assembly into the directory, called the AppBase, where the application is located. No registry settings are needed. In addition, an application configuration file can be added to override settings in an application's manifest and permit an assembly's files to be moved within the AppBase.

A shared assembly is one installed in a global location, called the Global Assembly Cache (GAC), where it is accessible by multiple applications. The most significant feature of the GAC is that it permits multiple versions of an assembly to execute side-by-side. To support this, .NET overcomes the name conflict problem that plagues DLLs by using four attributes to identify an assembly: the file name, a culture identity, a version number, and a public key token.

Public assemblies are usually located in the assembly directory located beneath the system directory of the operating system (WINNT on a Microsoft Windows 2000 operating system). As shown in Figure 1-7, the assemblies are listed in a special format that displays their four attributes (.NET Framework includes a DLL file that extends Windows Explorer to enable it to display the GAC contents). Let's take a quick look at these four attributes:

  • Assembly NameAlso referred to as the friendly name, this is the file name of the assembly minus the extension.

  • VersionEvery assembly has a version number that applies to all files in the assembly. It consists of four numbers in the format

    <major number>.<minor number>.<build>.<revision>

    Typically, the major and minor version numbers are updated for changes that break backward compatibility. A version number can be assigned to an assembly by including an AssemblyVersion attribute in the assembly's source code.

  • Culture SettingThe contents of an assembly may be associated with a particular culture or language. This is designated by a two-letter code such as “en” for English or “fr” for French, and can be assigned with an AssemblyCulture attribute placed in source code:

    [assembly: AssemblyCulture ("fr-CA")]
    
  • Public Key TokenTo ensure that a shared assembly is unique and authentic, .NET requires that the creator mark the assembly with a strong name. This process, known as signing, requires the use of a public/private key pair. When the compiler builds the assembly, it uses the private key to generate a strong name. The public key is so large that a token is created by hashing the public key and taking its last eight bytes. This token is placed in the manifest of any client assembly that references a shared assembly and is used to identify the assembly during execution.

Partial listing of Global Assembly Directory

Figure 1-7. Partial listing of Global Assembly Directory

Core Note

Core Note

An assembly that is signed with a public/private key is referred to as a strongly named assembly. All shared assemblies must have a strong name.

Precompiling an Assembly

After an assembly is loaded, the IL must be compiled to the machine's native code. If you are used to working with executables already in a machine code format, this should raise questions about performance and whether it's possible to create equivalent “executables” in .NET. The answer to the second part of the statement is yes; .NET does provide a way to precompile an assembly.

The .NET Framework includes a Native Image Generator (Ngen) tool that is used to compile an assembly into a “native image” that is stored in a native image cache—a reserved area of the GAC. Any time the CLR loads an assembly, it checks the cache to see if it has an associated native image available; if it does, it loads the precompiled code. On the surface, this seems a good idea to improve performance. However, in reality, there are several drawbacks.

Ngen creates an image for a hypothetical machine architecture, so that it will run, for example, on any machine with an x86 processor. In contrast, when the JIT in .NET runs, it is aware of the specific machine it is compiling for and can accordingly make optimizations. The result is that its output often outperforms that of the precompiled assembly. Another drawback to using a native image is that changes to a system's hardware configuration or operating system—such as a service pack update—often invalidate the precompiled assembly.

Core Recommendation

Core Recommendation

As a rule, a dynamically compiled assembly provides performance equal to, or better than, that of a precompiled executable created using Ngen.

Code Verification

As part of the JIT compile process, the Common Language Runtime performs two types of verification: IL verification and metadata validation. The purpose is to ensure that the code is verifiably type-safe. In practical terms, this means that parameters in a calling and called method are checked to ensure they are the same type, or that a method returns only the type specified in its return type declaration. In short, the CLR searches through the IL and metadata to make sure that any value assigned to a variable is of a compatible type; if not, an exception occurs.

Core Note

Core Note

By default, code produced by the C# compiler is verifiably type-safe. However, there is an unsafe keyword that can be used to relax memory access restrictions within a C# program (such as referencing beyond an array boundary).

A benefit of verified code is that the CLR can be certain that the code cannot affect another application by accessing memory outside of its allowable range. Consequently, the CLR is free to safely run multiple applications in a single process or address space, improving performance and reducing the use of OS resources.

Framework Class Library

The Framework Class Library (FCL) is a collection of classes and other types (enumerations, structures, and interfaces) that are available to managed code written in any language that targets the CLR. This is significant, because it means that libraries are no longer tied to specific compilers. As a developer, you can familiarize yourself with the types in a library and be assured that you can use this knowledge with whatever .NET language you choose.

The resources within the FCL are organized into logical groupings called namespaces. For the most part, these groupings are by broad functionality. For example, types used for graphical operations are grouped into the System.Drawing and System.Drawing.Drawing2D namespaces; types required for file I/O are members of the System.IO namespace. Namespaces represent a logical concept, not a physical one.

The FCL comprises hundreds of assemblies (DLLs), and each assembly may contain multiple namespaces. In addition, a namespace may span multiple assemblies. To demonstrate, let's look inside an FCL assembly.

Figure 1-8 displays a portion of the output generated by using Ildasm.exe to examine the contents of the mscorlib assembly. Although this only a partial listing, you can see that mscorlib contains System, the preeminent namespace in .NET, which serves as a repository for the types that give .NET its basic functionality. The assembly is also home to the System.Collections namespace, which includes classes and interfaces used for manipulating collections of data.

Output from Ildasm shows the namespaces and types that comprise an assembly

Figure 1-8. Output from Ildasm shows the namespaces and types that comprise an assembly

Table 1-2 lists some of the most important namespaces in .NET. For reference, the last column in each row includes a chapter number in this book where you'll find the namespace(s) used.

Table 1-2. Selected FCL Namespaces

Namespace

Use

Chapter

System

Contains the basic data types used by all applications. It also contains exception classes, predefined attributes, a Math library, and classes for managing the application environment.

3, 18

System.Collections
System.Collections.Specialized
System.Collections.Generic

Interfaces and classes used to manage collections of objects. These collections include the ArrayList, Hashtable, and Stack.

4

System.Data
System.Data.OracleClient
System.Data.SqlClient
System.Data.OleDb
System.Data.Odbc

Classes used for database operations (ADO.NET). The client namespaces support Oracle and SQL Server, respectively; OledDb and Odbc define the data connection used.

11, 12

System.Diagnostics

Contains classes that can be used to trace program execution, debug, and work with system logs and performance counters.

13

System.Drawing
System.Drawing.Drawing2D
System.Drawing.Printing
System.Drawing.Text

Provides graphics functionality for GDI+. These namespaces contain a class used for drawing as well as pens, brushes, geometric shapes, and fonts.

8, 9

System.Globalization

Contains classes that define culture-related information that affects the way dates, currency, and symbols are represented.

5

System.IO

Provides file and data stream I/O. These classes provide a way to access the underlying file systems of the host operating system.

5

System.Net

Classes that support network protocols and operations. Examples include WebRequest and WebResponse that request and fetch a Web page.

17

System.Reflection
System.Reflection.Emit

Contains types that permit the runtime inspection of metadata. The Emit namespace allows a compiler or tool to generate metadata and IL dynamically.

7, 15, App. B

System.Runtime.InterOpServices

Provides interoperability between managed and unmanaged code such as legacy DLLs or COM.

8

System.Security
System.Security.Permissions
System.Security.Cryptography

Classes used to manage .NET security. Defines classes that control access to operations and resources.

5, 15

System.Text.RegularExpressions

Classes that support .NET's regular expression engine.

5

System.Threading
System.Threading.Thread

Manages threading activites: thread creation, synchronization, and thread pool access.

13

System.Web
System.Web.Services
System.Web.UI
System.Web.UI.WebControls
System.Web.Security

The Internet-related classes referred to as ASP.NET. They manage browser-server communication requirements, manipulate cookies, and contain the controls that adorn a Web page.

Web.Services includes those classes required for SOAP-based XML messaging.

Web.UI includes classes and interfaces used for creating controls and pages that comprise Web forms.

16, 17, 18

System.Windows.Forms

Classes used to build Windows desktop GUI applications. Controls including the ListBox, TextBox, DataGrid, and buttons are found here.

6, 7

System.Xml

Types for processing XML.

10

Namespaces provide a roadmap for navigating the FCL. For example, if your applications are Web based, you'll spend most of your time exploring the types in the System.Web.* namespaces. After you have learned the basics of .NET and gained proficiency with C#, you'll find that much of your time is spent familiarizing yourself with the built-in types contained in the Framework Class Library.

Working with the .NET Framework and SDK

The .NET Framework Software Development Kit (SDK) contains the tools, compilers, and documentation required to create software that will run on any machine that has the .NET Framework installed. It is available as a free download (100 megabytes) from Microsoft that can be installed on Windows XP, Windows 2000, Windows Server 2003, and subsequent Windows operating systems. If you have Visual Studio.NET installed, there is no need to download it because VS.NET automatically does it for you.

Clients using software developed with the SDK do not require the SDK on their machine; however, they do require a compatible version of the .NET Framework. This .NET Framework Redistributable is available as a free download[3] (20+ megabytes) and should be distributed to clients along with applications that require it. This redistributable can be installed on Windows 98 and ME, in addition to the ones listed for the SDK. With minor exceptions, .NET applications will run identically on all operating system platforms, because they are targeted for the Common Language Runtime and not the operating system. There are some system requirements such as a minimum Internet Explorer version of 5.01. These are listed at the download site.

Updating the .NET Framework

Unlike many development environments, installing a new version of the framework is almost effortless. The installation process places the updated version in a new directory having the name of the version. Most importantly, there is no file dependency between the new and older versions. Thus, all versions are functional on your system. Although it varies by operating system, the versions are usually in the path

winntMicrosoft.NETFrameworkv1.0.3705
winntMicrosoft.NETFrameworkv1.1.4322
winntMicrosoft.NETFrameworkv2.0.40607

The installation of any new software version raises the question of compatibility with applications developed using an older version. .NET makes it easy to run existing applications against any framework version. The key to this is the application configuration file (discussed in much greater detail in Chapter 15). This text file contains XML tags and elements that give the CLR instructions for executing an application. It can specify where external assemblies are located, which version to use, and, in this case, which versions of the .NET Framework an application or component supports. The configuration file can be created with any text editor, although it's preferable to rely on tools (such as the Framework Configuration tool) designed for the task. Your main use of the configuration file will be to test current applications against new framework releases. Although it can be done, it usually makes no sense to run an application against an earlier version than it was originally compiled against.

.NET Framework Tools

The .NET Framework automates as many tasks as possible and usually hides the details from the developer. However, there are times when manual intervention is required. These may be a need to better understand the details of an assembly or perform the housekeeping required to prepare an application for deployment. We have encountered several examples of such tasks throughout the chapter. These include the need to

  • Add a file to an assembly

  • View the contents of an assembly

  • View the details of a specific class

  • Generate a public/private key pair in order to create a strongly named assembly

  • Edit configuration files

Many of these are better discussed within the context of later chapters. However, it is useful to be aware of which tools are available for performing these tasks; and a few, such as those for exploring classes and assemblies, should be mastered early in the .NET learning curve.

Table 1-3 lists some of the useful tools available to develop and distribute your applications. Three of these, Ildasm.exe, wincv.exe, and the .NET Framework Configuration tool, are the subject of further discussion.

Table 1-3. Selected .NET Framework Tools

Tool

Description

Al.exe

Assembly Linker

Can be used for creating an assembly composed of modules from different compilers. It is also used to build resource-only (satellite) assemblies.

Fuslogvw.exe

Assembly Binding Log Viewer

Used to troubleshoot the assembly loading process. It traces the steps followed while attempting to load an assembly.

Gacutil.exe

Global Assembly Cache tool

Is used to install or delete an assembly in the Global Assembly Cache. It can also be used for listing the GAC's contents.

Ildasm.exe

MSIL Disassembler

A tool for exploring an assembly, its IL, and metadata.

Mscorcfg.msc

NET Framework Configuration tool

A Microsoft Management Console (MMC) snap-in used to configure an assembly while avoiding direct manual changes to an application's configuration file. Designed primarily for administrators, a subset, Framework Wizards. Available for individual programmers.

Ngen.exe

Native Image Generator

Compiles an assembly's IL into native machine code. This image is then placed in the native image cache.

Sn.exe

Strong Name tool

Generates the keys that are used to create a strong—or signed—assembly.

wincv.exe

Windows Forms Class Viewer

A visual interface to display searchable information about a class.

Wsdl.exe

Web Services Description Language tool

Generates descriptive information about a Web Service that is used by a client to access the service.

Many of these tools are located in an SDK subdirectory:

c:Program FilesMicrosoft.NETSDKv2.0Bin

To execute the tools at the command line (on a Windows operating system) while in any directory, it is first necessary to place the path to the utilities in the system Path variable. To do this, follow these steps:

  1. Right click on the My Computer icon and select Properties.

  2. Select Advanced – Environment Variables.

  3. Choose the Path variable and add the SDK subdirectory path to it.

If you have Visual Studio installed, a simpler approach is to use the preconfigured Visual Studio command prompt. It automatically initializes the path information that enables you to access the command-line tools.

Ildasm.exe

The Intermediate Language Disassembler utility is supplied with the .NET Framework SDK and is usually located in the Bin subdirectory along the path where the SDK is installed. It is invaluable for investigating the .NET assembly environment and is one of the first tools you should become familiar with as you begin to work with .NET assemblies and code.

The easiest way to use the utility is to type in

C:>Ildasm /adv

at a command-line prompt (the optional /adv switch makes advanced viewing options available). This invokes the GUI that provides a File menu you use to select the assembly to view. Note that it does not open files in the Global Assembly Cache.

Figure 1-9 shows an example of the output created when an assembly is opened in Ildasm. The contents are displayed in a readable, hierarchical format that contains the assembly name, corecsharp1, and all of its members.

View assembly contents with Ildasm.exe

Figure 1-9. View assembly contents with Ildasm.exe

This hierarchy can then be used to drill down to the underlying IL (or CIL) instructions for a specific member. As an example, let's consider the Conversion class. The figure shows that it consists of three methods: Metric, conversion, and metric. The original source code confirms this:

public class Conversion
{
   public double Metric( double inches)
   { return (2.54 * inches); }
   [CLSCompliantAttribute(false)]
   public double metric( double miles)
   { return (miles / 0.62); }
   public double conversion( double pounds)
   { return (pounds * 454);}
}

Double clicking on the Metric method brings up a screen that displays its IL (Figure 1-10).

View of the IL

Figure 1-10. View of the IL

Ildasm can be used as a learning tool to solidify the concepts of IL and assemblies. It also has some practical uses. Suppose you have a third-party component (assembly) to work with for which there is no documentation. Ildasm provides a useful starting point in trying to uncover the interface details of the assembly.

Core Suggestion

Core Suggestion

Ildasm has a File – Dump menu option that makes it useful for saving program documentation in a text file. Select Dump Metainfo to create a lengthy human-readable form of the assembly's metadata; select Dump Statistics to view a profile of the assembly that details how many bytes each part uses.

Ildasm and Obfuscation

One of the natural concerns facing .NET developers is how to protect their code when a tool such as Ildasm—and other commercially available disassemblers—can be used to expose it. One solution is to use obfuscation—a technique that uses renaming and code manipulation sleight of hand to make the contents of an assembly unreadable by humans.

It is important to understand that obfuscation is not encryption. Encryption requires a decryption step so that the JIT compiler can process the code. Obfuscation transforms the IL code into a form that can be compiled using the tools of your development environment (see Figure 1-11).

Obfuscation conceals the original Intermediate Language

Figure 1-11. Obfuscation conceals the original Intermediate Language

The obfuscated code is functionally equivalent to the assembly's IL code and produces identical results when run by the CLR. How does it do this? The most common trick is to rename meaningful types and members with names that have no intrinsic meaning. If you look at obfuscated code, you'll see a lot of types named “a” or “b,” for example. Of course, the obfuscation algorithm must be smart enough not to rename types that are used by outside assemblies that depend on the original name. Another common trick is to alter the control flow of the code without changing the logic. For example, a while statement may be replaced with a combination of goto and if statements.

An obfuscator is not included in the .NET SDK. Dotfuscator Community Edition, a limited-feature version of a commercial product, is available with Visual Studio.NET. Despite being a relatively unsophisticated product—and only available for the Microsoft environment—it is a good way to become familiar with the process. Several vendors now offer more advanced obfuscator products.

wincv.exe

WinCV is a class viewer analogous to the Visual Studio Object Viewer, for those not using Visual Studio. It is located in the Program FilesMicrosoft.NetSDKV1.xBin directory and can be run from the command prompt. When the window appears, type the name of the class you want to view into the Searching For box (see Figure 1-12).

Using WinCV to view type definition of the Array class

Figure 1-12. Using WinCV to view type definition of the Array class

WinCV provides a wealth of information about any type in the base class libraries. The four highlighted areas provide a sampling of what is available:

  1. System.Array is the class that is being explored.

  2. This class is located in the mscorlib.dll assembly. We have already mentioned that this assembly contains the .NET managed types.

  3. This list contains the class, object, and interfaces that the Array class inherits from.

  4. The definition of each method in the class is included. This definition, which includes accessibility, type, and parameters, is called the method's signature.

Framework Configuration Tool

This tool provides an easy way to manage and configure assemblies as well as set security policies for accessing code. This tool is packaged as a Microsoft Management Console (MMC) snap-in. To access it, select Administrative Tools from the Control Panel; then select the Microsoft .NET Framework Configuration tool. This tool is designed for administrators who need to do the following:

  • Manage assemblies. Assemblies can be added to the GAC or deleted.

  • Configure assemblies. When an assembly is updated, the publisher of the assembly is responsible for updating the binding policy of the assembly. This policy tells the CLR which version of an assembly to load when an application references an assembly. For example, if assembly version 1.1 replaces 1.0, the policy redirects version 1.0 to 1.1 so that it is loaded. This redirection information is contained in a configuration file.

  • View .NET Framework security and modify an assembly's security. .NET security allows an assembly to be assigned certain permissions or rights. In addition, an assembly can require that other assemblies accessing it have certain permissions.

  • Manage how individual applications interact with an assembly or set of assemblies. You can view a list of all assemblies an application uses and set the version that your application uses.

To illustrate a practical use of the configuration tool, let's look at how it can be used to address one of the most common problems that plagues the software development process: the need to drop back to a previous working version when a current application breaks. This can be a difficult task when server DLLs or assemblies are involved. .NET offers a rather clever solution to this problem: Each time an application runs, it logs the set of assemblies that are used by the program. If they are unchanged from the previous run, the CLR ignores them; if there are changes, however, a snapshot of the new set of assemblies is stored.

When an application fails, one option for the programmer is to revert to a previous version that ran successfully. The configuration tool can be used to redirect the application to an earlier assembly. However, there may be multiple assemblies involved. This is where the configuration tool comes in handy. It allows you to view previous assembly configurations and select the assemblies en masse to be used with the application.

To view and select previous configurations, select Applications – Fix an Application from the Configuration tool menu. Figure 1-13 combines the two dialog boxes that subsequently appear. The main window lists applications that have run and been recorded. The smaller window (a portion of a larger dialog) is displayed when you click on an application. This window lists the most recent (up to five) configurations associated with the application. You simply select the assembly configuration that you want the application to use.

Using application configuration tool to select assembly version

Figure 1-13. Using application configuration tool to select assembly version

This configuration tool is clearly targeted for administrators. Individual developers should rely on a subset of this tool that is packaged as three wizards: Adjust .NET Security, Trust An Assembly, and Fix An Application. Access these by selecting Framework Wizards from Administrative Tools.

Understanding the C# Compiler

Many developers writing nontrivial .NET applications rely on Visual Studio or some other Integrated Development Environment (IDE) to enter source code, link external assemblies, perform debugging, and create the final compiled output. If you fall into this category, it is not essential that you understand how to use the .NET SDK and raw C# compiler; however, it will increase your understanding of the .NET compilation process and give you a better feel for working with assemblies. As a byproduct, it will also acquaint you with the command line as a way to work with SDK programs. Many of the utilities presented in the previous section are invoked from the command line, and you will occasionally find it useful to perform compilation in that environment rather than firing up your IDE.

Figure 1-14 shows the basic steps that occur in converting source code to the final compiled output. The purpose of this section is to demonstrate how a text editor and the C# compiler can be used to build an application. Along the way, it will provide a detailed look at the many compiler options that are hidden by the IDE.

Compilation process

Figure 1-14. Compilation process

Locating the Compiler

The C# compiler, csc.exe, is located in the path where the .NET Framework is installed:

C:winntMicrosoft.NETFrameworkv2.0.40607

Of course, this may vary depending on your operating system and the version of Framework installed. To make the compiler available from the command line in any current directory, you must add this path to the system Path variable. Follow the steps described in the previous section for setting the path for the SDK utilities.

Type in the following statement at the command line to verify that the compiler can be accessed:

C:>csc /help

Compiling from the Command Line

To compile the C# console application client.cs into the executable client.exe, enter either of the following statements at the command prompt:

C:> csc client.cs
C:> csc /t:exe client.cs

Both statements compile the source into an executable (.exe) file—the default output from the compiler. As shown in Table 1-4, the output type is specified using the /t: flag. To create a DLL file, set the target value to library. For a WinForms application, specify /t:winexe. Note that you can use /t:exe to create a WinForms application, but the console will be visible as background window.

Table 1-4. Selected Options for the C# Command-Line Compiler

Option

Description

/addmodule

Specifies a module that is to be included in the assembly created. This is an easy way to create a multi-file assembly.

/debug

Causes debug information to be produced.

/define

Preprocessor directive can be passed to compiler: /define:DEBUG.

/delaysign

Builds an assembly using delayed signing of the strong name. This is discussed in Chapter 15.

/doc

Used to specify that an output file containing XML documentation is to be produced.

/keyfile

Specifies the path to the .snk file containing the key pair used for strong signing (see Chapter 15).

/lib

Specifies where assemblies included in the /reference option are located.

/out

Name of the file containing compiled output. The default is the name of the input file with .exe suffix.

/reference (/r)

References an external assembly.

/resource

Used to embed resource files into the assembly that is created.

/target (/t)

Specifies the type of output file created:

/t:exe builds a *.exe console application. This is the default output.

/t:library builds a *.dll assembly.

/t:module builds a module (Portable Executable file) that does not contain a manifest.

/t:winexe builds a *.exe Windows Forms assembly.

The real value of working with the raw compiler is the ability to work with multiple files and assemblies. For demonstration purposes, create two simple C# source files: client.cs and clientlib.cs.

client.cs
   using System;
   public class MyApp
   {
      static void Main(string[] args) 
      {
         ShowName.ShowMe("Core C#");
      }
   }

clientlib.cs
   using System;
   public class ShowName
   {
      public static void ShowMe(string MyName) 
      {
         Console.WriteLine(MyName);
      }
   }

It's not important to understand the code details, only that the client routine calls a function in clientlib that writes a message to the console. Using the C# compiler, we can implement this relationship in a number of ways that not only demonstrate compiler options but also shed light on the use of assemblies.

Example 1: Compiling Multiple Files

The C# compiler accepts any number of input source files. It combines their output into a single file assembly:

csc /out:client.exe client.cs clientlib.cs

Example 2: Creating and Using a Code Library

The code in clientlib can be placed in a separate library that can be accessed by any client:

csc /t:library clientlib.cs

The output is an assembly named clientlib.dll. Now, compile the client code and reference this external assembly:

csc /r:clientlib.dll  client.cs

The output is an assembly named client.exe. If you examine this with Ildasm, you see that the manifest contains a reference to the clientlib assembly.

Example 3: Creating an Assembly with Multiple Files

Rather than existing as a separate assembly, clientlib can also be packaged as a separate file inside the client.exe assembly. Because only one file in an assembly may contain a manifest, it is first necessary to complile clientlib.cs into a Portable Executable[4] (PE) module. This is done by selecting module as the target output:

csc /t:module clientlib.cs

The output file is clientfile.netmodule. Now, it can be placed in the client.exe assembly by using the compiler's addmodule switch:

csc /addmodule:clientlib.netmodule client.cs

The resultant assembly consists of two files: client.exe and clientlib.netmodule.

These examples, shown in Figure 1-15, illustrate the fact that even a simple application presents the developer with multiple architectural choices for implementing an application.

Options for deploying an application

Figure 1-15. Options for deploying an application

Summary

The .NET Framework consists of the Common Language Runtime (CLR) and the Framework Class Library (FCL). The CLR manages all the tasks associated with code execution. It first ensures that code is CLR compliant based on the Common Language Specification (CLS) standard. It then loads an application and locates all dependent assemblies. Its Just-in-Time (JIT) compiler converts the IL contained in an application's assembly, the smallest deployable code unit in .NET, into native machine code. During the actual program execution, the CLR handles security, manages threads, allocates memory, and performs garbage collection for releasing unused memory.

All code must be packaged in an assembly in order for the CLR to use it. An assembly is either a single file or grouping of multiple physical files treated as a single unit. It may contain code modules as well as resource files.

The FCL provides a reusable set of classes and other types that are available to all CLR-compliant code. This eliminates the need for compiler-specific libraries. Although the FCL consists of several physical DLLs containing over a thousand types, it's made manageable by the use of namespaces that impose a logical hierarchy over all the types.

To assist the developer in debugging and deploying software, .NET includes a set of utilities that enables an administrator to perform such tasks as managing assemblies, precompiling assemblies, adding files to an assembly, and viewing class details. In addition, a wealth of open source .NET tools is becoming available to aid the development process.

Test Your Understanding

1:

What portable environment must be installed on a client's machine to enable it to run a .NET application?

2:

What is managed code? What is unmanaged code?

3:

What is the difference between the Common Type System and the Common Language Specification?

4:

How does the CLR allow code from different compilers to interact?

5:

What is the role of the Global Assembly Cache?

6:

What four components make up the identity of a strongly named assembly?

7:

What is the relationship between a namespace and an assembly?

8:

Describe what these commonly used acronyms stand for: CLR, GAC, FCL, IL.



[1] ECMA International was formerly known as the European Computer Manufacturers Association and is referred to herein simply as the ECMA.

[4] The PE format defines the layout for executable files that run on 32- or 64-bit Windows systems.

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

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