Reflection Emit: how to build constructor for this

2.4k views Asked by At

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.

2

There are 2 answers

2
Andrey Tretyak On BEST ANSWER

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.

3
Tony THONG On

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".