Reflection: can't get a MethodInfo for 'Add' in class BindingList<> if the type argument is a TypeBuilder

392 Views Asked by At

We have a compiler that uses reflection emit to generate assemblies. We have stumbled with trying to obtain the MethodInfo for the Add method in the BindingList<T> class, when T is a TypeBuilder object. We are using TypeBuilder.GetMethod( typeof(BindingList<myTypeBuilder>), typeof(BindingList<T>).GetMethod( "Add" )) but it throws an ArgumentException: "The specified method cannot be dynamic or global and must be declared on a generic type definition." Are we doing anything wrong? This works for List. Another observation, typeof( List<> ).GetMethod( "Add" ).DeclaringType.IsGenericTypeDefinition is true, while typeof( BindingList<> ).GetMethod( "Add" ).DeclaringType.IsGenericTypeDefinition is false, which doesn't make sense to me.

Here's C# code that recreates the issue

var assemblyBuilder = System.Threading.Thread.GetDomain().DefineDynamicAssembly( new AssemblyName("MyAssembly"), AssemblyBuilderAccess.Save, "C:\\output\\" );
var moduleBuilder = assemblyBuilder.DefineDynamicModule( "MyModule", "MyModule.dll", false );
// create MyClass in the module
TypeBuilder myClass = moduleBuilder.DefineType( "MyClass" );
Type bindingListOfT = typeof( BindingList<> );
Type bindingListOfMyClass = bindingListOfT.MakeGenericType( myClass );
// create the DoStuff method in MyClass
MethodBuilder doStuffMethod = myClass.DefineMethod( "DoStuff", MethodAttributes.Private );
ILGenerator generator = doStuffMethod.GetILGenerator();
// var myList = new BindingList<MyClass>()
LocalBuilder myListDeclaration = generator.DeclareLocal( bindingListOfMyClass );
ConstructorInfo listOfMyClassConstructor = TypeBuilder.GetConstructor( bindingListOfMyClass,
    bindingListOfT.GetConstructor( Type.EmptyTypes ) );
generator.Emit( OpCodes.Newobj, listOfMyClassConstructor );
generator.Emit( OpCodes.Stloc, myListDeclaration );
// myList.Add( new MyClass() )
ConstructorInfo myClassConstructor = myClass.DefineConstructor( MethodAttributes.Public,
    CallingConventions.Standard, Type.EmptyTypes );
generator.Emit( OpCodes.Ldloc, myListDeclaration );
generator.Emit( OpCodes.Newobj, myClassConstructor );
// the next line throws exception. If using List<> instead on BindingList<> all is well

MethodInfo add1 = TypeBuilder.GetMethod( bindingListOfMyClass, bindingListOfT.GetMethod( "Add" ) );
generator.Emit( OpCodes.Callvirt, add1 );

// finish
generator.Emit( OpCodes.Ret );
myClass.CreateType();
assemblyBuilder.Save( "MyModule.dll", PortableExecutableKinds.ILOnly, ImageFileMachine.I386 );

We have found a workaround which involves getting the declaring type's generic type definition, finding the MethodInfo in this type, making a generic type out of the generic type definition, and then calling TypeBuilder.GetMethod. It's a horrible piece of code, as first we need to find the correct MethodInfo not only based on name but also on arguments, and then climb back the inheritance chain of the original type so we can correctly match the type arguments in the base class to call MakeGenericType, all the way back to the method's declaring type. There has to be an easier way.

1

There are 1 best solutions below

3
On BEST ANSWER

Add is actually declared on System.Collections.ObjectModel.Collection<>, so try using that type instead of BindingList<> when creating your two Type instances.

EDIT

As to why BindingList<T>'s base class is not a generic type definition, it's a bit subtle. While the base class is Collection<T>, the T is actually different from the T used in Collection<T>'s definition (it's the T declared as part of BindingList<T>'s definition). Perhaps an easier-to-grasp example would be something along these lines:

class SameTypeDict<T> : Dictionary<T,T> {}

Here, SameTypeDict<T>'s base class is Dictionary<T,T>, which is different from the generic type definition Dictionary<TKey,TValue>.

I think something along these lines should do what you want:

/// <summary>
/// Given a concrete generic type instantiation T&lt;t_1, t_2, ...>, where any number of the t_i may be type builders,
/// and given a method M on the generic type definition T&lt;>, return the corresponding method on the concrete type
/// </summary>
static MethodInfo GetConcreteMethodInfo(Type concreteClass, MethodInfo genericTypeMethod)
{
    var substitution = concreteClass.GetGenericTypeDefinition().GetGenericArguments()
                        .Zip(concreteClass.GetGenericArguments(), (t1, t2) => Tuple.Create(t1, t2))
                        .ToDictionary(t => t.Item1, t => t.Item2);
    var declaredMethod = genericTypeMethod.DeclaringType.Module.ResolveMethod(genericTypeMethod.MetadataToken);
    var declaringTypeGeneric = declaredMethod.DeclaringType;  // typeof(Collection<>)
    var declaringTypeRelative = genericTypeMethod.DeclaringType; // typeof(Collection<BindingList<T>.T>)
    // now get the concrete type by applying the substitution
    var declaringTypeConcrete = // typeof(Collection<myClass>)
        declaringTypeGeneric.MakeGenericType(Array.ConvertAll(declaringTypeRelative.GetGenericArguments(),
                                                t => substitution.ContainsKey(t) ? substitution[t] : t));
    return TypeBuilder.GetMethod(declaringTypeConcrete, (MethodInfo)declaredMethod);
}

Note that you may need to make some modifications if the method itself can also be generic.