سؤال

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.

هل كانت مفيدة؟

المحلول

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;
}

نصائح أخرى

The easist way to work out how to generate the IL for a method is to create a simple console application that has your method in. Then build it and run ILDasm against it to look at the IL instructions necessary to make up the method.

Once you can see the instructions it shouldn't be too difficult to write code to emit the OpCodes necessary.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top