Beautiful Code [25]
To generate the Intermediate Language that comprises the body of this method, you obtain an object of type ILGenerator:
ILGenerator generator = dynameth.GetILGenerator();
Most of what follows will use this generator object. You can begin by defining local variables of the method. I determined that it would be convenient to have three local variables corresponding to three of the local variables in the FilterMethodCS:
generator.DeclareLocal(typeof(int)); // Index 0 = iDst
generator.DeclareLocal(typeof(double)); // Index 1 = pixelsAccum
generator.DeclareLocal(typeof(double)); // Index 2 = filterAccum
As the comments indicate, these local variables will be referred to by indexes. We are now ready to begin defining a loop based around iDst that will access all the pixels of the destination array. These three statements correspond to the declarations of these variables in lines 3, 4, and 6 of Example 8-2.
Much of the remainder of this exercise requires generating Intermediate Language operation codes, which are similar to machine language op codes. Intermediate Language consists of one-byte op codes, sometimes with arguments. However, you don't need to get your hands dirty with the actual bits and bytes. To generate these op codes, call one of the overloads of the Emit method defined by the IlGenerator class. The first argument to Emit is always an object of type OpCode, and all the available op codes are predefined as static read-only fields of the OpCodes class (notice the plural). As of this writing, the OpCodes class is documented online at http://msdn2.microsoft.com/library/system.reflection.emit.opcodes.aspx.
Most of the assignment and operational logic in Intermediate Language is based on a virtual evaluation stack. (I say it's virtual because the actual code that will eventually be executed by your computer processor is machine code generated by the just-in-time compiler, and this code might or might not mimic the evaluation stack of the Intermediate Language.) A load instruction pushes a value onto the stack. This can be either a specific number or a value from a local variable, or something else. A store instruction retrieves the value from the stack and stores it in a local variable or someplace else. Arithmetic and logical operations are also performed on the stack. Add, for example, pops two values from the stack, adds them, and pushes the result onto the stack.
Setting the local iDst variable to 0 in Intermediate Language requires a load instruction and a store instruction. The Ldc_I4_0 instruction places a four-byte integer value of 0 on the stack, and the Stloc_0 instruction stores that value in the local variable with index 0, which is the local variable corresponding to iDst:
generator.Emit(OpCodes.Ldc_I4_0);
generator.Emit(OpCodes.Stloc_0);
Although many high-level programming languages include a goto (or equivalent) instruction, modern programmers are discouraged from using it. However, in assembly language and Intermediate Language, the goto—generally known as a jump or ranch instruction—is the only form of flow control available. All for and if statements must be mimicked with branching.
The .NET Intermediate Language supports an unconditional branch statement and several conditional branch statements. These conditional branches depend on the results of a specified prior comparison. For example a branch if less than instruction performs a branch if, in a previous comparison, one value was less than another. Mimicking an if and else construction in Intermediate Language requires two labels, one corresponding to the beginning of the else block, and the other pointing after the else block. If the if condition is not true, a conditional branch goes to the first label; otherwise, the if block is executed. At the end of the if block, an unconditional branch goes to the label following the else block. The two possibilities are illustrated by Figure 8-4.
Figure 8-4. Intermediate