我们有一个使用反射发射来生成程序集的编译器。当T是TypeBuilder对象时,我们偶然发现尝试在BindingList<T>
类中获取Add方法的MethodInfo。我们正在使用TypeBuilder.GetMethod( typeof(BindingList<myTypeBuilder>), typeof(BindingList<T>).GetMethod( "Add" ))
,但它会抛出ArgumentException:&#34;指定的方法不能是动态的或全局的,必须在泛型类型定义上声明。&#34;我们做错了吗?这适用于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,不仅要基于名称而且还需要查找参数,然后再回溯原始类型的继承链,这样我们才能正确匹配基类调用MakeGenericType,一直回到方法的声明类型。必须有一个更简单的方法。
答案 0 :(得分:2)
Add
实际上已在System.Collections.ObjectModel.Collection<>
上声明,因此在创建两个BindingList<>
实例时请尝试使用该类型而不是Type
。
修改强>
至于为什么BindingList<T>
的基类不是泛型类型定义,它有点微妙。虽然基类为Collection<T>
,但T
实际上与T
定义中使用的Collection<T>
不同(T
声明为BindingList<T>
的一部分{ {1}}的定义)。也许一个更容易理解的例子就是这样的:
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);
}
请注意,如果方法本身也可以是通用的,则可能需要进行一些修改。