反射:can't get a MethodInfo for 'Add'在类BindingList

本文关键字:for Add BindingList 在类 MethodInfo can 反射 get | 更新日期: 2023-09-27 18:18:08

我们有一个使用反射发出来生成程序集的编译器。当T是一个TypeBuilder对象时,我们在试图为BindingList<T>类中的Add方法获取MethodInfo时遇到了麻烦。我们正在使用TypeBuilder.GetMethod( typeof(BindingList<myTypeBuilder>), typeof(BindingList<T>).GetMethod( "Add" )),但它抛出一个ArgumentException:"指定的方法不能是动态的或全局的,必须在泛型类型定义上声明。"我们做错了什么吗?这适用于List。另一个观察,typeof( List<> ).GetMethod( "Add" ).DeclaringType.IsGenericTypeDefinition是正确的,而typeof( BindingList<> ).GetMethod( "Add" ).DeclaringType.IsGenericTypeDefinition是假的,这对我来说没有意义。

下面是重新创建问题的c#代码

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

我们找到了一个解决方法,它包括获取声明类型的泛型类型定义,在该类型中找到MethodInfo,从泛型类型定义中生成泛型类型,然后调用TypeBuilder.GetMethod。这是一段可怕的代码,因为首先我们不仅需要根据名称找到正确的MethodInfo,还需要根据参数找到正确的MethodInfo,然后爬回原始类型的继承链,这样我们就可以正确匹配基类中的类型参数以调用MakeGenericType,一直回到方法的声明类型。一定有更简单的方法

反射:can't get a MethodInfo for 'Add'在类BindingList

Add实际上是在System.Collections.ObjectModel.Collection<>上声明的,所以在创建两个Type实例时尝试使用该类型而不是BindingList<>

编辑

至于为什么BindingList<T>的基类不是泛型类型定义,这有点微妙。虽然基类是Collection<T>,但T实际上不同于Collection<T>定义中使用的T(它是作为BindingList<T>定义的一部分声明的T)。也许一个更容易理解的例子是这样的:

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

这里,SameTypeDict<T>的基类是Dictionary<T,T>,与泛型类型定义Dictionary<TKey,TValue>不同。

我认为这样做可以满足你的需要:

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

注意,如果方法本身也可以是泛型的,你可能需要做一些修改。