
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf

CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES |
633 |
// A structure definition is always sealed.
.class public sealed MyStruct
extends [mscorlib]System.ValueType{}
Do be aware that CIL provides a shorthand notation to define a structure type. If you use the value attribute, the new type will derive the type from [mscorlib]System.ValueType automatically. Therefore, you could define MyStruct as follows:
// Shorthand notation for declaring a structure.
.class public sealed value MyStruct{}
Defining Enums in CIL
.NET enumerations (as you recall) derive from System.Enum, which is a System.ValueType (and therefore must also be sealed). When you wish to define an enum in terms of CIL, simply extend
[mscorlib]System.Enum:
// An enum.
.class public sealed MyEnum extends [mscorlib]System.Enum{}
Like a structure definition, enumerations can be defined with a shorthand notation using the enum attribute:
// Enum shorthand.
.class public sealed enum MyEnum{}
You’ll see how to specify the name/value pairs of an enumeration in just a moment.
■Note The other fundamental .NET type, the delegate, also has a specific CIL representation. See Chapter 11 for full details.
Defining Generics in CIL
Generic types also have a specific representation in the syntax of CIL. Recall from Chapter 10 that a given generic type or generic member may have one or more type parameters. For example, the List<T> type has a single type parameter, while Dictionary<TKey, TValue> has two. In terms of CIL, the number of type parameters is specified using a backward-leaning single tick, `, followed by a numerical value representing the number of type parameters. Like C#, the actual value of the type parameters is encased within angled brackets.
■Note On most keyboards, the ` character can be found on the key above the Tab key (and to the left of the 1 key).
For example, assume you wish to create a List<T> type, where T is of type System.Int32. In CIL, you would author the following:
// In C#: List<int> myInts = new List<int>(); newobj instance void class [mscorlib]
System.Collections.Generic.List`1<int32>::.ctor()


CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES |
635 |
Once you have confirmed the contents of your assembly, run peverify.exe against it. Notice that you are issued a number of errors, given that all your types are completely empty (see Figure 19-6).
Figure 19-6. Empty types yield verification errors!
To understand how to populate a type with content, you first need to examine the fundamental data types of CIL.
.NET Base Class Library, C#, and CIL Data Type
Mappings
Table 19-4 illustrates how a .NET base class type maps to the corresponding C# keyword, and how each C# keyword maps into raw CIL. As well, Table 19-4 documents the shorthand constant notations used for each CIL type. As you will see in just a moment, these constants are often referenced by numerous CIL opcodes.
Table 19-4. Mapping .NET Base Class Types to C# Keywords, and C# Keywords to CIL
.NET Base Class Type |
C# Keyword |
CIL Representation |
CIL Constant Notation |
System.SByte |
sbyte |
int8 |
I1 |
System.Byte |
byte |
unsigned int8 |
U1 |
System.Int16 |
short |
int16 |
I2 |
System.UInt16 |
ushort |
unsigned int16 |
U2 |
System.Int32 |
int |
int32 |
I4 |
System.UInt32 |
uint |
unsigned int32 |
U4 |
System.Int64 |
long |
int64 |
I8 |
System.UInt64 |
ulong |
unsigned int64 |
U8 |
System.Char |
char |
char |
CHAR |
System.Single |
float |
float32 |
R4 |
System.Double |
double |
float64 |
R8 |
System.Boolean |
bool |
bool |
BOOLEAN |
System.String |
string |
string |
N/A |
System.Object |
object |
object |
N/A |
System.Void |
void |
void |
VOID |
|
|
|
|

636 CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES
Defining Type Members in CIL
As you are already aware, .NET types may support various members. Enumerations have some set of name/value pairs. Structures and classes may have constructors, fields, methods, properties, static members, and so on. Over the course of this book’s first 18 chapters, you have already seen partial CIL definitions for the items previously mentioned, but nevertheless, here is a quick recap of how various members map to CIL primitives.
Defining Field Data in CIL
Enumerations, structures, and classes can all support field data. In each case, the .field directive will be used. For example, let’s breathe some life into the skeleton MyEnum enumeration and define three name/value pairs (note the values are specified within parentheses):
.class public sealed enum MyEnum
{
.field public static literal valuetype MyNamespace.MyEnum A = int32(0)
.field public static literal valuetype MyNamespace.MyEnum B = int32(1)
.field public static literal valuetype MyNamespace.MyEnum C = int32(2)
}
Fields that reside within the scope of a .NET System.Enum-derived type are qualified using the static and literal attributes. As you would guess, these attributes set up the field data to be a fixed value accessible from the type itself (e.g., MyEnum.NameOne).
■Note The values assigned to an enum value may also be in hexadecimal with an 0x prefix.
Of course, when you wish to define a point of field data within a class or structure, you are not limited to a point of public static literal data. For example, you could update MyBaseClass to support two points of private, instance-level field data:
.class public MyBaseClass
{
.field private string stringField
.field private int32 intField
}
As in C#, class field data will automatically be initialized to an appropriate default value. If you wish to allow the object user to supply custom values at the time of creation for each of these points of private field data, you (of course) need to create custom constructors.
Defining Type Constructors in CIL
The CTS supports both instance-level and class-level (static) constructors. In terms of CIL, instance-level constructors are represented using the .ctor token, while a static-level constructor is expressed via .cctor (class constructor). Both of these CIL tokens must be qualified using the rtspecialname (return type special name) and specialname attributes. Simply put, these attributes are used to identify a specific CIL token that can be treated in unique ways by a given .NET language. For example, in C#, constructors do not define a return type; however, in terms of CIL, the return value of a constructor is indeed void:

CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES |
637 |
.class public MyBaseClass
{
.field private string stringField
.field private int32 intField
.method public hidebysig specialname rtspecialname instance void .ctor(string s, int32 i) cil managed
{
// TODO: Add implementation code...
}
}
Note that the .ctor directive has been qualified with the instance attribute (as it is not a static constructor). The cil managed attributes denote that the scope of this method contains CIL code, rather than unmanaged code, which may be used during platform invocation requests.
Defining Properties in CIL
Properties and methods also have specific CIL representations. By way of an example, if MyBaseClass were updated to support a public property named TheString, you would author the following CIL (note again the use of the specialname attribute):
.class public MyBaseClass
{
...
.method public hidebysig specialname
instance string get_TheString() cil managed
{
// TODO: Add implementation code...
}
.method public hidebysig specialname
instance void set_TheString(string 'value') cil managed
{
// TODO: Add implementation code...
}
.property instance string TheString()
{
.get instance string MyNamespace.MyBaseClass::get_TheString()
.set instance void
MyNamespace. MyBaseClass::set_TheString(string)
}
}
Recall that in terms of CIL, a property maps to a pair of methods that take get_ and set_ prefixes. The .property directive makes use of the related .get and .set directives to map property syntax to the correct “specially named” methods.
■Note Notice that the incoming parameter to the set method of a property is placed in single-tick quotation marks, which represents the name of the token to use on the right-hand side of the assignment operator within the method scope.

638 CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES
Defining Member Parameters
In a nutshell, specifying arguments in CIL is (more or less) identical to doing so in C#. For example, each argument is defined by specifying its data type followed by the parameter name. Furthermore, like C#, CIL provides a way to define input, output, and pass-by-reference parameters. As well, CIL allows you to define a parameter array argument (aka the C# params keyword) as well as optional parameters (which are not supported in C#, but are used in VB .NET).
To illustrate the process of defining parameters in raw CIL, assume you wish to build a method that takes an int32 (by value), an int32 (by reference), a [mscorlib]System.Collection.ArrayList, and a single output parameter (of type int32). In terms of C#, this method would look something like the following:
public static void MyMethod(int inputInt,
ref int refInt, ArrayList ar, out int outputInt)
{
outputInt = 0; // Just to satisfy the C# compiler...
}
If you were to map this method into CIL terms, you would find that C# reference parameters are marked with an ampersand (&) suffixed to the parameter’s underlying data type (int32&). Output parameters also make use of the & suffix, but they are further qualified using the CIL [out] token. Also notice that if the parameter is a reference type (in this case, the [mscorlib]System. Collections.ArrayList type), the class token is prefixed to the data type (not to be confused with the .class directive!):
.method public hidebysig static void MyMethod(int32 inputInt, int32& refInt,
class [mscorlib]System.Collections.ArrayList ar, [out] int32& outputInt) cil managed
{
...
}
Examining CIL Opcodes
The final aspect of CIL code you’ll examine in this chapter has to do with the role of various operational codes (opcodes). Recall that an opcode is simply a CIL token used to build the implementation logic for a given member. The complete set of CIL opcodes (which is fairly large) can be grouped into the following broad categories:
•Opcodes that control program flow
•Opcodes that evaluate expressions
•Opcodes that access values in memory (via parameters, local variables, etc.)
To provide some insight to the world of member implementation via CIL, Table 19-5 defines some of the more useful opcodes that are directly related to member implementation logic, grouped by related functionality.



CHAPTER 19 ■ UNDERSTANDING CIL AND THE ROLE OF DYNAMIC ASSEMBLIES |
641 |
wish to define three local variables of type System.String, System.Int32, and System.Object. In C#, this member would appear as follows (recall that locally scoped variables do not receive a default value and should be set to an initial state before further use):
public static void MyLocalVariables()
{
string myStr = "CIL code is fun!"; int myInt = 33;
object myObj = new object();
}
If you were to construct MyLocalVariables() directly in CIL, you could author the following:
.method public hidebysig static void MyLocalVariables() cil managed
{
.maxstack 8
// Define three local variables.
.locals init ([0] string myStr, [1] int32 myInt, [2] object myObj)
//Load a string onto the virtual execution stack. ldstr "CIL code is fun!"
//Pop off current value and store in local variable [0]. stloc.0
//Load a constant of type "i4"
//(shorthand for int32) set to the value 33.
ldc.i4 33
//Pop off current value and store in local variable [1]. stloc.1
//Create a new object and place on stack.
newobj |
instance void [mscorlib]System.Object::.ctor() |
// Pop off current value and store in local variable [2]. stloc.2
ret
}
As you can see, the first step taken to allocate local variables in raw CIL is to make use of the
.locals directive, which is paired with the init attribute. Within the scope of the related parentheses, your goal is to associate a given numerical index to each variable (seen here as [0], [1], and [2]). As you can see, each index is identified by its data type and an optional variable name. Once the local variables have been defined, you load a value onto the stack (using the various loadcentric opcodes) and store the value within the local variable (using the various storage-centric opcodes).
Mapping Parameters to Local Variables in CIL
You have already seen how to declare local variables in raw CIL using the .local init directive; however, you have yet to see exactly how to map incoming parameters to local methods. Consider the following static C# method:
public static int Add(int a, int b)
{
return a + b;
}