CHAPTER 18 UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES
Each category of CIL token is expressed using a particular syntax, and the tokens are combined to
build a valid .NET assembly.
The Role of CIL Directives
First up, there is a set of well-known CIL tokens that are used to describe the overall structure of a .NET
assembly. These tokens are called directives. CIL directives are used to inform the CIL compiler how to
define the namespaces(s), type(s), and member(s) that will populate an assembly.
Directives are represented syntactically using a single dot (.) prefix (e.g., .namespace, .class,
.publickeytoken, .method, .assembly, etc.). Thus, if your *.il file (the conventional extension for a file
containing CIL code) has a single .namespace directive and three .class directives, the CIL compiler will
generate an assembly that defines a single .NET namespace containing three .NET class types.
The Role of CIL Attributes
In many cases, CIL directives in and of themselves are not descriptive enough to fully express the
definition of a given .NET type or type member. Given this fact, many CIL directives can be further
specified with various CIL attributes to qualify how a directive should be processed. For example, the
.class directive can be adorned with the public attribute (to establish the type visibility), the extends
attribute (to explicitly specify the type’s base class), and the implements attribute (to list the set of
interfaces supported by the type).
Note Don’t confuse a “.NET attribute” (see Chapter 15) with that of a “CIL attribute,” which are two very
different concepts.
The Role of CIL Opcodes
Once a .NET assembly, namespace, and type set have been defined in terms of CIL using various directives
and related attributes, the final remaining task is to provide the type’s implementation logic. This is a job
for operation codes, or simply opcodes. In the tradition of other low-level languages, many CIL opcodes
tend to be cryptic and completely unpronounceable by us mere humans. For example, if you need to load a
string variable into memory, you don’t use a friendly opcode named LoadString, but rather ldstr.
Now, to be fair, some CIL opcodes do map quite naturally to their C# counterparts (e.g., box, unbox,
throw, and sizeof). As you will see, the opcodes of CIL are always used within the scope of a member’s
impl [Y[