Reflection Emit:如何为此构建构造函数

4
我想动态构建的代码如下所示:
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;
}
< p > ObservableTestCollection 的源代码如下:
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);

我无法创建Sample实例。

有人可以帮我纠正这个问题吗?

非常感谢您的帮助。


“我无法创建 Sample 实例” 是什么意思? 在运行时是否出现错误消息?编译错误或警告? - Martin Verjans
请编辑您的问题并插入消息。 - Martin Verjans
我收到的消息如下所述: {"尝试加载格式不正确的程序。(HRESULT 异常: 0x8007000B)"} - Joon w K
2个回答

2
您的程序无效,因为您试图创建一个“ObservableTestCollection of Sample”的实例,但是Sample是一个TypeBuilder。
请使用TypeBuilder.GetConstructor(Type, ConstructorInfo)来获取一个通用构造函数,如果一个通用参数是一个TypeBuilder,而不是使用“MakeGenericType”。

嗨,Tony,我尝试使用'var ci = TypeBuilder.GetConstructor(genericTypeOTS, ctorBuilder);',但是出现了错误:指定的构造函数必须在泛型类型定义上声明。参数名称:constructor。我认为Sample类型不是泛型类型,但是"ObservableTestCollection"类型是泛型类型。谢谢。 - Joon w K
@JoonwK 第二个参数应该是来自开放泛型类型(ObservableTestCollection<>)的 ConstructorInfo。我在我的答案中添加了详细的解释和示例。 - Andrey Tretyak
你好,这篇文章中已经有一个类似的案例:https://dev59.com/W53ha4cB1Zd3GeqPQzYK - Tony THONG

2
导致此错误的原因是调用开放泛型类型的构造函数。您需要使用 TypeBuilder 作为泛型参数获取封闭泛型类型的构造函数。获取这个 ConstructorInfo 存在一些问题,可以在这里找到解释。
所以解决方案是调用 TypeBuilder.GetConstructor(Type, ConstructorInfo) 静态方法(就像 @TonyTHONG 已经提到的那样),并传入以下参数:
  • Type 必须是封闭在 TypeBuilder 上的泛型类型,在您的情况下是 typeof(ObservableTestCollection<>).MakeGenericType(typeBuilder)
  • ConstructorInfo 可以从开放泛型类型中获取,在您的情况下是 typeof(ObservableTestCollection<>)
您可以查看下面的代码示例来解决您的问题:
        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);

请注意,我只能将ObservableTestCollection放在与生成Sample类的代码不同的程序集中才能运行它。

如果我没有弄错,您也是动态生成ObservableTestCollection类的。所以,如果您为它们使用相同的AssemblyBuilder,它们可以在不分离程序集的情况下工作。


谢谢Andrey。太完美了!! - Joon w K
我建立了ObservableTestCollection,并使用相同的AssemblyBuilder创建了样本,发现它可以工作。这对我来说是一个很大的帮助。再次感谢。 - Joon w K

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接