反思:无法获得一个MethodInfo来添加'在类BindingList<>如果type参数是TypeBuilder

时间:2014-08-15 21:37:57

标签: c# generics reflection reflection.emit typebuilder

我们有一个使用反射发射来生成程序集的编译器。当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,一直回到方法的声明类型。必须有一个更简单的方法。

1 个答案:

答案 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&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);
}

请注意,如果方法本身也可以是通用的,则可能需要进行一些修改。