There is a very common misconception surrounding assemblies. When people learning .NET for the first time approach veterans, they often ask the question, "What is an assembly?" Unfortunately, most of the time, the response is incorrect. If you've asked that question and been told that an assembly is essentially just a DLL or an EXE file, you've been given only part of the story.
You might remember that I earlier referred to an assembly as a logical unit of storage and deployment. I did not say that it was a physical unit of storage and deployment. This difference might seem like semantics, but it makes quite a big difference when you begin working with larger, more complex applications that have many deployment files.
In most cases, an assembly is actually just one DLL or EXE file. This is what is called a single-file assembly. This is the most common type of assembly and is sufficient for a majority of programming tasks that you might need to accomplish with the .NET Framework.
However, some programming tasks require the use of an assembly that is actually more than one file. Such assemblies are referred to as multifile assemblies. When you build an application that will be used internationally, or an application that uses various logically grouped resources such as images, text files, XML files, or strings that appear in different languages depending on the location of the user, you will be making use of multifile assemblies.
Before you get your hands dirty with some code involving assemblies, there is one other aspect of assemblies that is often overlooked in introductory texts. There are two different kinds of assemblies (regardless of whether it is multifile or single file): static and dynamic.
A static assembly is one that exists on disk. This assembly can be a single file or a grouping of files. You'll see how that grouping of files is stored later in the chapter. You can copy a static assembly from one location to another so long as all the files that are part of the assembly are copied with it.
The other kind of assembly is a dynamic assembly. A dynamic assembly is one that exists only in memory. Through some fairly complex reflection code, you can actually emit code directly into memory, compile it (creating a dynamic assembly), and execute it without ever having placed a file on disk. It might sound obscure, but there are quite a few very good uses for technology like this.
Figure 12.1 is an illustration of a complex multifile assembly. Each component of the assembly is discussed in the text following the figure.
Figure 12.1. Illustration of a complex multifile assembly.
Introducing the Assembly Manifest
The assembly manifest is a collection of data information. This information describes the assembly itself, as well as all the various components of the assembly and how they relate to each other. The manifest contains the assembly metadata (discussed in the next section) and can be stored in what is called a portable executable (PE) file. A PE file is either a DLL or an EXE. Many people confuse the term assembly with a PE file. Although the two are related, they are definitely not the same thing.
No discussion of assemblies or assembly manifests would be complete without discussing an incredibly useful tool that comes with the .NET Framework SDK: ILDASM.EXE.
To examine an assembly manifest, run the ILDASM tool. You can either browse to its location, or open a Visual Studio .NET 2003 Command Prompt and type ILDASM at the command prompt. A small Windows Forms application will open up.
With ILDASM running, open the file \Windows\Microsoft.NET\Framework\v1.1.4322\System.Data.dll. After the file has opened, you should see an organized tree list of the assembly's contents. Double-click the Manifest section (indicated with a red triangle facing right). You will be confronted with a window that should look similar to the one in Figure 12.2.
Figure 12.2. The assembly manifest for the System.Data assembly.
The information presented here might seem a bit overwhelming at first. It isn't necessary to completely grasp everything shown so far. However, understanding that something called the assembly manifest exists, and that it is part of every .NET assembly (whether single-file or multifile) will certainly make learning some advanced .NET topics much easier.
With the manifest still open, take a look at what looks like lines of code in an odd language. You can see statements like the following:
.assembly extern mscorlib
These statements are metadata. They are pieces of data that describe the functionality, contents, and references of the assembly. This particular statement indicates to the Common Language Runtime that this assembly makes use of the mscorlib library. In the preceding chapter, you saw how to create and use code attributes. Attributes are also metadata, and assembly-scope attributes are stored in the manifest. If you scroll down somewhat in the Manifest window, you can see some custom code attributes and their associated values (displayed in a hexadecimal ASCII string).
When an assembly is loaded at runtime, the Common Language Runtime uses the manifest to determine what must be done to properly handle that assembly, including locating all the assemblies that are required by the assembly being loaded, its components, embedded files, and linked external files.
Inside the AssemblyMSIL Code
In addition to all the information contained in the manifest, an assembly can contain code. When you compile your C# assembly, the C# is converted into Microsoft Intermediate Language (MSIL). This is the language that the Common Language Runtime understands and executes when your application is running. Unlike traditional C or C++ programs, which compile directly to machine code, .NET code is compiled into MSIL. When the MSIL is loaded at runtime, it is JIT (just-in-Time) compiled into machine code and then cached for future use. This extra degree of separation from the machine code is what allows code written for the .NET Framework to claim (limited) portability.
To take a look at some MSIL, close the Manifest window and then use the tree navigation structure to drill down to System.Data.AcceptRejectRule.Cascade. Double-click the Cascade line (indicated by a teal diamond with an S in the middle). The following text appears in a new window:
.field public static literal valuetype System.Data.AcceptRejectRule Cascade = int32(0x00000001)
This text indicates to the Common Language Runtime that the enumeration called AcceptRejectRule has a public static enumerated value called Cascade that has the value of 1. You don't need to know MSIL in order to write effective code targeting the .NET Framework. However, knowing that high-level language code written in C# or Visual Basic .NET (or any other .NET language) is converted into MSIL at compile time and then converted into machine code at runtime is essential to gaining a full understanding of .NET.
Another thing that you can store in an assembly is a resource. Resources can be a table of multilingual strings, a bitmap, a text file, an XML file, a JPG file, or virtually anything. Resources in an assembly can either be embedded directly in the body of the assembly itself or they can be linked externally. An externally linked resource will be listed in the assembly manifest. When the Common Language Runtime loads the assembly, it will see the reference in the manifest and attempt to load the file indicated as a resource. A very important thing to remember is that regardless of where an assembly resource is located, the location is transparent to a programmer consuming the resource. As you'll see in code later in this chapter, the method calls to obtain resources are the same whether the resource is physically stored inside a .DLL or linked externally.