反射: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,一直回到方法的声明类型。一定有更简单的方法
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<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<>, 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);
}
注意,如果方法本身也可以是泛型的,你可能需要做一些修改。