Reflection Emit: how to build constructor for this

我们两清 提交于 2019-12-23 16:02:43

问题


The code I want to build dynamically is as follow:

public class Sample
{
    public Sample()
    {
        Items = new ObservableTestCollection<Sample>(this);
    }
    public Sample(IEnumerable<Sample> source)
    {
        Items = new ObservableTestCollection<Sample>(this, source);
    }
    public ObservableTestCollection<Sample> Items;
}

The Source of ObservableTestCollection is as follow:

public class ObservableTestCollection<T> : ObservableCollection<T>
{
    public T Parent;       
    public ObservableTestCollection(T parent)
    {
        Parent = parent;
    }
    public ObservableTestCollection(T parent, IEnumerable<T> source) : base(source)
    {
        Parent = parent;
    }
}

The code I write is:

const string assemblyName = "SampleAssembly";
const string fieldName = "Items";
const string typeName = "Sample";
const string assemblyFileName = assemblyName + ".dll";

AppDomain domain = AppDomain.CurrentDomain;
AssemblyBuilder assemblyBuilder = domain.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.RunAndSave);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName, assemblyFileName);

TypeBuilder typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Class | TypeAttributes.Public);

Type[] ctorParameters = new Type[] { typeBuilder };
Type typeOfCTS = typeof(ObservableTestCollection<>);
Type genericTypeOTS = typeOfCTS.MakeGenericType(typeBuilder);


FieldBuilder fieldBuilder = typeBuilder.DefineField(fieldName, genericTypeOTS, FieldAttributes.Public);

        //first constructor
ConstructorBuilder ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, Type.EmptyTypes);
ILGenerator generator = ctorBuilder.GetILGenerator();
        generator.Emit(OpCodes.Ldarg_0); //load this
        generator.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)); //call object constructor

var ci = typeOfCTS.GetConstructors()[0];
generator.Emit(OpCodes.Newobj, ci);            
generator.Emit(OpCodes.Stfld, fieldBuilder); // store into Items
generator.Emit(OpCodes.Ret); //return

//second constructor
var typeOfIE = typeof(IEnumerable<>);
var genericTypeIE = typeOfIE.MakeGenericType(typeBuilder);          
ctorParameters = new Type[] {genericTypeIE };
ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, ctorParameters);

ctorParameters = new Type[] { typeBuilder, genericTypeIE };
generator = ctorBuilder.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0); //load this

ci = typeOfCTS.GetConstructors()[1];
generator.Emit(OpCodes.Newobj, ci);
generator.Emit(OpCodes.Stfld, fieldBuilder); // store into Items
generator.Emit(OpCodes.Ret); //return
Type type = typeBuilder.CreateType();
var obj = Activator.CreateInstance(type);
assemblyBuilder.Save(assemblyFileName);

I can't create instance of Sample.

Can anyone help me correct this problem?

Your help would be much appreciated.


回答1:


Reason for this error is call of constructor for open generic type. You need to get constructor for closed generic type with TypeBuilder as generic parameter. There are some problem with getting this ConstructorInfo that explained here.

So solution is to call TypeBuilder.GetConstructor(Type, ConstructorInfo) static method (as @TonyTHONG already mentioned) with following parameters:

  • Type had to be generic type closed on TypeBuilder, in your case typeof(ObservableTestCollection<>).MakeGenericType(typeBuilder);
  • ConstructorInfo that you can get from open generic type, in your case typeof(ObservableTestCollection<>).

You can see code example for your problem below:

        const string assemblyName = "SampleAssembly";
        const string fieldName = "Items";
        const string typeName = "Sample";
        const string assemblyFileName = assemblyName + ".dll";

        var domain = AppDomain.CurrentDomain;
        var assemblyBuilder = domain.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.RunAndSave);

        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName, assemblyFileName);
        var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Class | TypeAttributes.Public);

        var typeOfCts = typeof(ObservableTestCollection<>);
        var genericTypeOfCts = typeOfCts.MakeGenericType(typeBuilder);

        var fieldBuilder = typeBuilder.DefineField(fieldName, genericTypeOfCts, FieldAttributes.Public);

        //first constructor Sample()
        var ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, Type.EmptyTypes);
        var obsCtor1 = typeOfCts.GetConstructors().First(c => c.GetParameters().Length == 1);
        obsCtor1 = TypeBuilder.GetConstructor(genericTypeOfCts, obsCtor1); //hack to get close generic type ctor with typeBuilder as generic parameter

        var generator = ctorBuilder.GetILGenerator();
        generator.Emit(OpCodes.Ldarg_0); //load this for base type constructor
        generator.Emit(OpCodes.Call, typeof(object).GetConstructors().Single());

        generator.Emit(OpCodes.Ldarg_0); //load this for field setter

        generator.Emit(OpCodes.Ldarg_0); //load this for ObservableTestCollection constructor
        generator.Emit(OpCodes.Newobj, obsCtor1); //call ObservableTestCollection constructor, it will put point to new object in stack

        generator.Emit(OpCodes.Stfld, fieldBuilder); // store into Items
        generator.Emit(OpCodes.Ret); //return

        //second constructor Sample(IEnumerable<Sample> source)
        var ctorParam = typeof(IEnumerable<>).MakeGenericType(typeBuilder);
        ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new Type[] { ctorParam } );
        obsCtor1 = typeOfCts.GetConstructors().First(c => c.GetParameters().Length == 2);
        obsCtor1 = TypeBuilder.GetConstructor(genericTypeOfCts, obsCtor1); //hack to get close generic type ctor with typeBuilder as generic parameter

        generator = ctorBuilder.GetILGenerator();
        generator.Emit(OpCodes.Ldarg_0); //load this for base type constructor
        generator.Emit(OpCodes.Call, typeof(object).GetConstructors().Single());

        generator.Emit(OpCodes.Ldarg_0); //load this for field setter

        generator.Emit(OpCodes.Ldarg_0); //load this for ObservableTestCollection constructor
        generator.Emit(OpCodes.Ldarg_1); //load IEnumerable for ObservableTestCollection constructor
        generator.Emit(OpCodes.Newobj, obsCtor1); //call ObservableTestCollection constructor, it will put point to new object in stack

        generator.Emit(OpCodes.Stfld, fieldBuilder); // store into Items
        generator.Emit(OpCodes.Ret); //return


        var type = typeBuilder.CreateType();
        var obj1 = Activator.CreateInstance(type);

        var parameter = Activator.CreateInstance(typeof(List<>).MakeGenericType(type));
        var obj2 = Activator.CreateInstance(type, parameter);
        assemblyBuilder.Save(assemblyFileName);

Also please keep in mind that I've managed to run it only by placing ObservableTestCollection in different assembly from code that generates Sample class.

If I'm not mistaken you are also generating ObservableTestCollection class dynamically. So it may work without separating assemblies, especially if you use same AssemblyBuilder for them.




回答2:


Your program is invalid because you try to create an instance "ObservableTestCollection of Sample" but Sample is a typebuilder.

Please use TypeBuilder.GetConstructor(Type, ConstructorInfo) to get a generic constructor if a generic argument is a typebuilder instead of "MakeGenericType".



来源:https://stackoverflow.com/questions/41357408/reflection-emit-how-to-build-constructor-for-this

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!