MSIL - 4. For Loops
So for completeness I thought that I would introduce you to basic loops, although I have no need of them in my intended use of
Reflection.Emit. I am going to finish off my Calculator implementation by non-optimal implementation of the multiply method, I could make use of the
OpCodes.Mul operator in the same way as I have used
OpCodes.Add etc. but this would not demonstrate a basic for loop. As part of this I will also demonstrate calling a method of the generated class.
The source code for this can be found in my
subversion repository.
Implementing Multiply
I can create a loop that will make use of the
Add() method to calculate the multiplication:
public int Multiply(int a, int b)
{
int result = 0;
for (int i = a; i > 0; i--)
{
result = Add(result, b);
}
return result;
}
So taking a look at ILDASM:
.method public hidebysig newslot virtual final
instance int32 Multiply(int32 a,
int32 b) cil managed
{
// Code size 36 (0x24)
.maxstack 3
.locals init ([0] int32 result,
[1] int32 i,
[2] int32 CS$1$0000,
[3] bool CS$4$0001)
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: ldarg.1
IL_0004: stloc.1
IL_0005: br.s IL_0016
IL_0007: nop
IL_0008: ldarg.0
IL_0009: ldloc.0
IL_000a: ldarg.2
IL_000b: call instance int32 Inheritance.CalculatorImplementation::Add(int32,
int32)
IL_0010: stloc.0
IL_0011: nop
IL_0012: ldloc.1
IL_0013: ldc.i4.1
IL_0014: sub
IL_0015: stloc.1
IL_0016: ldloc.1
IL_0017: ldc.i4.0
IL_0018: cgt
IL_001a: stloc.3
IL_001b: ldloc.3
IL_001c: brtrue.s IL_0007
IL_001e: ldloc.0
IL_001f: stloc.2
IL_0020: br.s IL_0022
IL_0022: ldloc.2
IL_0023: ret
} // end of method CalculatorImplementation::Multiply
The first thing is that we now have 4 local variables:
- [0] int32 result - the integer variable to hold the result
- [1] int32 i - the integer variable to hold the loop counter
- [2] int32 CS$1$0000 - an integer variable, created by the compiler, that holds the return value
- [3] bool CS$4$0001 - a boolean variable, created by the compiler, that holds the result of the loop condition
for(int i = a; i > 0; i--)
Initially, the
result variable is assigned the constant 0 (
OpCodes.Ldc_I4_0) and the
loop counter is assigned the value of the first parameter (
OpCodes.Ldarg_1). The IL then switches execution to the location
IL0016 that executes the condition and stores the result of that condition into the
loop condition variable. If the
loop condition variable is true execution then returns to location
IL0007 to execute the main body of the loop. Note the use of the
OpCodes.Cgt - greater than code.
In the main body of the loop the
this parameter (parameter 0) is pushed onto the stack followed by the current
result variable and the second parameter. The stack is now set-up for a call into the
Add method and the result is stored into the
result variable. The
loop counter variable is then decremented and the condition is evaluated once more.
When the
loop condition is false, the
result variable is stored into the
return variable which is then returned to the caller.
So putting this into code:
private static void DefineMultiplyMethod(TypeBuilder typeBuilder, Type interfaceType)
{
var interfaceMethod = interfaceType.GetMethod("Multiply");
var addMethod = interfaceType.GetMethod("Add");
var addParameters = addMethod
.GetParameters()
.Select(parameter => parameter.ParameterType)
.ToArray();
var methodBuilder = typeBuilder.DefineMethod("Multiply",
MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.Final |
MethodAttributes.NewSlot |
MethodAttributes.HideBySig);
methodBuilder.SetReturnType(interfaceMethod.ReturnType);
var parameters = interfaceMethod
.GetParameters()
.Select(parameter => parameter.ParameterType)
.ToArray();
methodBuilder.SetParameters(parameters);
methodBuilder.InitLocals = true;
var il = methodBuilder.GetILGenerator();
var returnLabel = il.DefineLabel();
var decisionLabel = il.DefineLabel();
var loopStartLabel = il.DefineLabel();
il.DeclareLocal(typeof(int));
il.DeclareLocal(typeof(int));
il.DeclareLocal(typeof(int));
il.DeclareLocal(typeof(bool));
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stloc_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Stloc_1);
il.Emit(OpCodes.Br_S, decisionLabel);
il.MarkLabel(loopStartLabel);
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldarg_2);
il.EmitCall(OpCodes.Call, addMethod, addParameters);
il.Emit(OpCodes.Stloc_0);
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldloc_1);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Sub);
il.Emit(OpCodes.Stloc_1);
il.MarkLabel(decisionLabel);
il.Emit(OpCodes.Ldloc_1);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Cgt);
il.Emit(OpCodes.Stloc_3);
il.Emit(OpCodes.Ldloc_3);
il.Emit(OpCodes.Brtrue_S, loopStartLabel);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Stloc_2);
il.MarkLabel(returnLabel);
il.Emit(OpCodes.Ldloc_2);
il.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(methodBuilder, interfaceMethod);
}
I now have a fully functional and fully working calculator class that implements the
Calculator interface and has been generated dynamically at runtime.
Next time, I will be looking into lambda expressions...
Tags: