Search code examples
c#reflectionreflection.emitikvm

Defining a method with for loops and conditional statements


I have to define a method with Reflection.Emit that is rather complex, because I have to do a for loop on a field and have a condition with break and return. My method that I want to recreate with reflection looks like this in regular code:

override int GetKeyImpl(Type obj0)
{
    int answer = -1;
    for(int i = 0; i < knownTypes.length; i++){
          if(knowntypes[i] == obj0){
                answer = i;
                break;
          }
    }
    return answer;
} 

My idea to solve this problem was to generate a method with reflection that redirects the call to my original method and returns the int.

I need to know how to do a for loop and breaks with OpCodes to recreate the method while doing conditional checks on an array that is inside a class. I've searched for tutorials but didn't find any that go further than addition of two ints.

Edit: Forgot to mention it, I'm using IKVM.Reflection and knownTypes is an array of Type[]. The method that im writing is one that will override an abstract one.


Solution

  • This should reproduce the method you specified:

    TypeBuilder type = /* ... */;
    FieldInfo knownFields = /* ... */;
    
    // Finding dependencies via reflection
    var baseMethod = type.BaseType.GetMethod(
        "GetKeyImpl",
        BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
    
    var typeEqualsOperator = typeof(Type).GetMethod(
        "op_Equality",
        BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
        null,
        new[] { typeof(Type), typeof(Type) },
        null);
    
    // Declaring the method
    var getKeyImpl = type.DefineMethod(
        baseMethod.Name,
        baseMethod.Attributes & ~(MethodAttributes.Abstract |
                                  MethodAttributes.NewSlot));
    
    // Setting return type
    getKeyImpl.SetReturnType(typeof(int));
    
    // Adding parameters
    getKeyImpl.SetParameters(typeof(Type));
    getKeyImpl.DefineParameter(1, ParameterAttributes.None, "obj0");
    
    // Override the base method
    type.DefineMethodOverride(getKeyImpl, baseMethod);
    
    var il = getKeyImpl.GetILGenerator();
    
    // Preparing locals
    var answer = il.DeclareLocal(typeof(int));
    var i = il.DeclareLocal(typeof(int));
    
    // Preparing labels
    var loopCondition = il.DefineLabel();
    var loopIterator = il.DefineLabel();
    var returnLabel = il.DefineLabel();
    var loopBody = il.DefineLabel();
    
    // Writing body
    
    // answer = -1
    il.Emit(OpCodes.Ldc_I4_M1);
    il.Emit(OpCodes.Stloc, answer);
    
    // i = 0
    il.Emit(OpCodes.Ldc_I4_0);
    il.Emit(OpCodes.Stloc, i);
    
    // jump to loop condition
    il.Emit(OpCodes.Br_S, loopCondition);
    
    // begin loop body
    il.MarkLabel(loopBody);
    
    // if (obj0 != knownTypes[i]) continue
    il.Emit(OpCodes.Ldarg_0); // omit if 'knownTypes' is static
    il.Emit(OpCodes.Ldfld, knownTypes); // use 'Ldsfld' if 'knownTypes' is static
    il.Emit(OpCodes.Ldloc, i);
    il.Emit(OpCodes.Ldelem_Ref);
    il.Emit(OpCodes.Ldarg_1); // use 'Ldarg_0' if 'knownTypes' is static
    il.Emit(OpCodes.Call, typeEqualsOperator);
    il.Emit(OpCodes.Brfalse_S, loopIterator);
    
    // answer = i; jump to return
    il.Emit(OpCodes.Ldloc, i);
    il.Emit(OpCodes.Stloc, answer);
    il.Emit(OpCodes.Br_S, returnLabel);
    
    // begin loop iterator
    il.MarkLabel(loopIterator);
    
    // i = i + 1
    il.Emit(OpCodes.Ldloc, i);
    il.Emit(OpCodes.Ldc_I4_1);
    il.Emit(OpCodes.Add);
    il.Emit(OpCodes.Stloc, i);
    
    // begin loop condition
    il.MarkLabel(loopCondition);
    
    // if (i < knownTypes.Length) jump to loop body
    il.Emit(OpCodes.Ldloc, i);
    il.Emit(OpCodes.Ldarg_0); // omit if 'knownTypes' is static
    il.Emit(OpCodes.Ldfld, knownTypes); // use 'Ldsfld' if 'knownTypes' is static
    il.Emit(OpCodes.Ldlen);
    il.Emit(OpCodes.Conv_I4);
    il.Emit(OpCodes.Blt_S, loopBody);
    
    // return answer
    il.MarkLabel(returnLabel);
    il.Emit(OpCodes.Ldloc, answer);
    il.Emit(OpCodes.Ret);
    
    // Finished!
    

    The decompiled results are as expected:

    override int GetKeyImpl(Type obj0)
    {
        for (int i = 0; i < this.knownTypes.Length; i++)
        {
            if (this.knownTypes[i] == obj0)
                return i;
        }
        return -1;
    }
    

    If you have access to .NET Reflector, there is a Reflection.Emit Language Add-In that may interest you. Alternatively, write a prototype in C# code, and then run it through a disassembler to see the raw IL.

    If it had been okay to make the method static (and accept knownTypes as a parameter or make it a static field), then you could have composed the method body using LINQ expression trees. Unfortunately, you cannot compose instance method bodies using this technique; they have to be static. Example:

    var method = typeBuilder.DefineMethod(
        "GetKeyImpl",
        MethodAttributes.Private |
        MethodAttributes.Static | 
        MethodAttributes.HideBySig);
    
    var type = E.Parameter(typeof(Type), "type");
    var knownTypes = E.Parameter(typeof(Type[]), "knownTypes");
    
    var answer = E.Variable(typeof(int), "answer");
    var i = E.Variable(typeof(int), "i");
    
    var breakTarget = E.Label("breakTarget");
    var continueTarget = E.Label("continueTarget");
    var returnTarget = E.Label(typeof(int), "returnTarget");
    
    var forLoop = E.Block(
        new[] { i },
        E.Assign(i, E.Constant(0)),
        E.Loop(
            E.Block(
                E.IfThen(
                    E.GreaterThanOrEqual(i, E.ArrayLength(knownTypes)),
                    E.Break(breakTarget)),
                E.IfThen(
                    E.Equal(E.ArrayIndex(knownTypes, i), type),
                    E.Return(returnTarget, i)),
                E.Label(continueTarget),
                E.PreIncrementAssign(i))),
        E.Label(breakTarget));
    
    var body = E.Lambda<Func<Type, Type[], int>>(
        E.Block(
            new[] { answer },
            E.Assign(answer, E.Constant(-1)),
            forLoop,
            E.Label(returnTarget, answer)),
        type,
        knownTypes);
    
    body.CompileToMethod(method);
    
    return method;
    

    The example above accepts knownTypes as the second parameter. Refactoring to read from a static field instead would be straightforward. The decompiled results, again, are as expected:

    private static int GetKeyImpl(Type type, Type[] knownTypes)
    {
        for (int i = 0; i < knownTypes.Length; i++)
        {
            if (knownTypes[i] == type)
                return i;
        }
        return -1;
    }