问题
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 onTypeBuilder
, in your casetypeof(ObservableTestCollection<>).MakeGenericType(typeBuilder)
;ConstructorInfo
that you can get from open generic type, in your casetypeof(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