Pergunta

I have some code that uses a MethodInfo of a generic method found on a generated type. To avoid some reflection, I have the code use the

ldtoken Method
ldtoken Type
call GetMethodFromHandle(RuntimeMethodHandle,RunTimeTypeHandle)

Pattern to generate the MethodInfos at compile time.

However, if the methodInfo belongs to a generic type and itself is a generic method things get screwy. Here is some code that simply generates a GM that emits an open version of its methodInfo. If I call it to retrieve the method than try to close it over a specific type I get a perplexing exception::

System.Reflection.MethodInfo GM[M]() is not a GenericMethodDefinition. MakeGenericMethod may only be called on a method for which MethodBase.IsGenericMethodDefinition is true.

Here is the relevant code::

var aBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Test"), AssemblyBuilderAccess.RunAndSave);
var mBuilder = aBuilder.DefineDynamicModule(aBuilder.GetName().Name, true);
var typeBuilder = mBuilder.DefineType("NameSpace.Generic`1",TypeAttributes.AutoClass | TypeAttributes.Sealed | TypeAttributes.Public,typeof(object));
var TypeGenerics = typeBuilder.DefineGenericParameters(new[] { "T" });
var methodBuilder = typeBuilder.DefineMethod("GM", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig);
var methodGenerics = methodBuilder.DefineGenericParameters(new[] { "M" });
methodBuilder.SetSignature(typeof(MethodInfo), null, null, Type.EmptyTypes, null, null);
var ilgenerator = methodBuilder.GetILGenerator();
var typeBuilderClosedOverT = typeBuilder.MakeGenericType(TypeGenerics);
ilgenerator.Emit(OpCodes.Ldtoken, methodBuilder);
ilgenerator.Emit(OpCodes.Ldtoken, typeBuilderClosedOverT);
ilgenerator.Emit(OpCodes.Call, 
    typeof(MethodBase).GetMethod(
        "GetMethodFromHandle", 
        BindingFlags.Public | BindingFlags.Static,
        null,
        new[] { typeof(RuntimeMethodHandle), typeof(RuntimeTypeHandle) },
        null
    )
);
ilgenerator.Emit(OpCodes.Castclass,typeof(MethodInfo));
ilgenerator.Emit(OpCodes.Ret);
var bakedType = typeBuilder.CreateType();
var methodInfo = bakedType.MakeGenericType(typeof(int)).GetMethod("GM").MakeGenericMethod(typeof(bool)).Invoke(null, null) as MethodInfo;
var methodInfoClosedOverBool = methodInfo.MakeGenericMethod(typeof(bool));

It seems the only time my code screws up is if it's a genericmethod on a non-generic type. If the code is rewritten so that its about a normal method on a normal type, or a generic method on a normal type, or a normal method on a generic type it all works. It's only the combination of both that causes errors. Am I doing something wrong?

I submitted a bug about this issue: https://connect.microsoft.com/VisualStudio/feedback/details/775989/clr-cannot-emit-a-token-for-an-open-generic-method-on-a-generic-type

Foi útil?

Solução

Looks like a CLR issue to me, because the same thing happens if you write the IL by hand and use ilasm. That is, given a generic class G and a nongeneric class N, each with a generic method M, then trying to get the generic method definition from the non-generic class works:

ldtoken    method void class N::M<[1]>()
ldtoken    class N<!T>
call       class [mscorlib]System.Reflection.MethodBase [mscorlib]
             System.Reflection.MethodBase::GetMethodFromHandle(
                valuetype [mscorlib]System.RuntimeMethodHandle,
                valuetype [mscorlib]System.RuntimeTypeHandle)
castclass  [mscorlib]System.Reflection.MethodInfo
ret

but the MethodInfo returned from the generic class is not a generic method definition (but it almost is; it's D.MakeGenericMethod(D.GetGenericArguments()) where D is the method definition you want):

ldtoken    method void class G`1<!T>::M<[1]>()
ldtoken    class G`1<!T>
call       class [mscorlib]System.Reflection.MethodBase [mscorlib]
             System.Reflection.MethodBase::GetMethodFromHandle(
                valuetype [mscorlib]System.RuntimeMethodHandle,
                valuetype [mscorlib]System.RuntimeTypeHandle)
castclass  [mscorlib]System.Reflection.MethodInfo
ret

Outras dicas

The problem lies within the ldtoken method instruction because, due to the inability of IL to express generic method definitions, the CLR loads the wrong method. The instruction is decompiled by ildasm to this:

ldtoken method class [mscorlib]System.Reflection.MethodInfo class NameSpace.Generic`1<!T>::GM<[1]>()

Which isn't even valid IL. The CLR then messes up the instruction and instead loads a generic method instantiation from it's own generic parameters.

var methodInfoClosedOverBool = (methodInfo.IsGenericMethodDefinition ? methodInfo : methodInfo.GetGenericMethodDefinition()).MakeGenericMethod(typeof(bool));

For more tests, I've made a shorter code showing the same issue:

DynamicMethod dyn = new DynamicMethod("", typeof(RuntimeMethodHandle), null);
var il = dyn.GetILGenerator();
il.Emit(OpCodes.Ldtoken, typeof(GenClass<string>).GetMethod("GenMethod"));
il.Emit(OpCodes.Ret);
var handle = (RuntimeMethodHandle)dyn.Invoke(null, null);
var m = MethodBase.GetMethodFromHandle(handle, typeof(GenClass<int>).TypeHandle);

GetMethodFromHandle (which should also only require the method handle, not the declaring type) just sets the declaring type (notice <int> or <string> doesn't matter) and doesn't do anything wrong.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top