Why is a TypeBuilder generated generic methodinfo not a generic method?
Asked Answered
G

2

9

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

Greatest answered 7/1, 2013 at 16:52 Comment(3)
That's interesting, it looks like methodInfo.IsGenericMethodDefinition is indeed false, although methodInfo.GetGenericArguments() returns a type that is generic parameter.Nickens
Yeah inspecting it certainly makes it look like the damn thing is generic. My current solution is to make sure my methods have unique names and passing in the Type and a string, and calling type.GetMethod(...), but I'd like to avoid the reflection.Greatest
Seems that it is now resolved in .net 4.7Greatest
U
3

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
Unpractical answered 7/1, 2013 at 19:28 Comment(2)
Well thanks I actually just did the same thing with concrete types (List<int>.ConvertAll) and I'm going to report this as a bug to microsoft. I doubt much will happen :(Greatest
What's interesting is that this IL cannot be compiled back: "error : syntax error at token '['"Al
A
0

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.

Al answered 6/4, 2015 at 13:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.