A .NET application is composed of four primary entities:
Classes are covered in detail in the next two chapters, As such we are going to limit the discussion here to clarify that they are defined in the source files for your application or class library. Upon compilation of your source files, you produce a module. The code that makes up an assembly's modules may exist in a single executable (.exe) file or as a dynamic link library (.dll).
A module, is in fact, a Microsoft intermediate language (MSIL) file, which is then used by the CLR when your application is run. However, compiling a .NET application doesn't produce only an MSIL file; it also produces a collection of files that make up a deployable application or assembly. Within an assembly are several different types of files, including not only the actual executable files, but also configuration files, signature keys, and related resources.
The type system provides a template that is used to describe the encapsulation of data and an associated set of behaviors. It is this common template for describing data that provides the basis for the metadata that .NET uses when classes interoperate at runtime.
All types are based on a common system that is used across all .NET languages, profiles and platforms. Similar to the MSIL code, which is interpreted by the CLR based upon the current runtime environment, the CLR uses a common metadata system to recognize the details of each type. The result is that all .NET languages are built around a common type system. Thus it isn't necessary to translate data through a special notation to enable transfer of data types between different .exe and .dll files.
A type has fields, properties, and methods:
The preceding elements are just part of each class. Note that some types are defined at the application level, and others are defined globally. With .NET it is not only possible but often encouraged that the classes and types defined in your modules be visible only at the application level. The advantage of this is that you can run several different versions of an application side by side.
A module contains Microsoft intermediate language (MSIL, often abbreviated to IL) code, associated metadata, and the assembly's manifest. IL is a platform-independent way of representing managed code within a module. Before IL can be executed, the CLR must compile it into the native machine code. The default method is for the CLR to use the JIT (just-in-time) compiler to compile the IL on a method-by-method basis. At runtime, as each method is called for the first time, it is passed through the JIT compiler for compilation to machine code. Similarly, for an ASP.NET application, each page is passed through the JIT compiler the first time it is requested.
Additional information about the types declared in the IL is provided by the associated metadata. The metadata contained within the module is used extensively by the CLR. For example, if a client and an object reside within two different processes, then the CLR uses the type's metadata to marshal data between the client and the object. MSIL is important because every .NET language compiles down to IL. The CLR doesn't care about or even need to know what the implementation language was; it knows only what the IL contains. Thus, any differences in .NET languages exist at the level where the IL is generated; but once compiled to IL; all .NET languages have the same runtime characteristics. Similarly, because the CLR doesn't care in which language a given module was originally written, it can leverage modules implemented in entirely different .NET languages.
A question that always arises when discussing the JIT compiler and the use of a runtime environment is “Wouldn't it be faster to compile the IL language down to native code before the user asks to run it?” Although the answer is not always yes, the answer is that combined with the flexibility of changes to the machine and other advantages related to evaluation of the environment when the code is accessed, this is the most robust solution.
However, because sometimes the only consideration is speed, Microsoft has provided a utility to handle this compilation: the Native Image Generator, or Ngen.exe. This tool enables you to essentially run the JIT compiler on a specific assembly, which is then installed into the user's application cache in its native format. The obvious advantage is that now when the user asks to execute something in that assembly, the JIT compiler is not invoked, saving a small amount of time. Unlike the JIT compiler, which compiles only those portions of an assembly that are actually referenced, Ngen.exe needs to compile the entire code base. This is important because the time required for NGEN compilation is not the same as what a user actually experiences with the JIT compiler.
Ngen.exe is executed from the command line. The utility was updated as part of .NET 2.0 and now automatically detects and includes most of the dependent assemblies as part of the image-generation process. To use Ngen.exe, you simply reference this utility followed by an action; for example, install followed by your assembly reference. Several options are available as part of the generation process, but that subject is beyond the scope of this chapter, given that Ngen.exe itself is a topic that generates heated debate regarding its use and value.
Where does the debate begin about when to use Ngen.exe? Keep in mind that in a server application, where the same assembly will be referenced by multiple users between machine restarts, the difference in performance on the first request is essentially lost. This means that compilation to native code is more valuable to client-side applications. Unfortunately, using Ngen.exe requires running it on each client machine, which can become cost prohibitive in certain installation scenarios, particularly if you use any form of self-updating application logic.
Another issue relates to using reflection, which enables you to reference other assemblies at runtime. Of course, if you don't know what assemblies you will reference until runtime, then the Native Image Generator has a problem, as it won't know what to reference, either. You may have occasion to use Ngen.exe for an application you've created, but you should fully investigate this utility and its advantages and disadvantages beforehand, keeping in mind that even native images execute within the CLR. Native image generation changes only the compilation model, not the runtime environment.
An assembly is the primary unit of deployment for .NET applications. It is either a dynamic link library (.dll) or an executable (.exe). An assembly is composed of a manifest, one or more modules, and (optionally) other files, such as .config, .ASPX, .ASMX, images, and so on. By default, the Visual Basic compiler creates an assembly that combines both the assembly code and the manifest.
The manifest of an assembly contains the following:
The manifest can be stored in a separate file or in one of the modules. By default, for most applications, it is part of the .dll or .exe file, which is compiled by Visual Studio. For ASP.NET applications, you'll find that although there is a collection of ASPX pages, the actual assembly information is located in a DLL referenced by those ASPX pages.
Managing component versions was challenging prior to .NET's ability to associate a manifest and use application-level references. Often, even though the version number of the component could be set, it was not used by the runtime.
For many applications, .NET has removed the need to identify the version of each assembly in a central registry on a machine. However, some assemblies are installed once and used by multiple applications. .NET provides a global assembly cache (GAC), which is used to store assemblies that are intended for use by multiple applications. The CLR provides global versioning support for all components loaded in the GAC.
The CLR provides two features for assemblies installed within the GAC:
The assembly's manifest contains the version numbers of referenced assemblies. The CLR uses the assembly's manifest at runtime to locate a compatible version of each referenced assembly. The version number of an assembly takes the following form:
Major.Minor.Build.Revision
Changes to the major and minor version numbers of the assembly indicate that the assembly is no longer compatible with the previous versions. The CLR will not use versions of the assembly that have a different major or minor number unless it is explicitly told to do so. For example, if an assembly was originally compiled against a referenced assembly with a version number of 3.4.1.9, then the CLR will not load an assembly stored in the GAC unless it has major and minor numbers of 3 and 4, respectively.
Incrementing the revision and build numbers indicates that the new version is still compatible with the previous version. If a new assembly that has an incremented revision or build number is loaded into the GAC, then the CLR can still load this assembly for applications that were compiled referencing a previous version.
For .NET applications running outside of Metro, most components should not be registered. The external assemblies are referenced locally, which means they are carried in the application's local directory structure. Using local copies of external assemblies enables the CLR to support the side-by-side execution of different versions of the same component.
Except when looking to add an application to Metro, the only requirement for installing a .NET application is to copy it to the local machine. A .NET application can be distributed using a simple command like this:
xcopy \serverappDirectory "C:Program FilesappDirectory" /E /O /I
However, because Metro has introduced the concept of a Windows Application Store details of deployment are being focused on in Chapter 22.