使用TypeBuilder构建含自引用属性的运行时类型遇困境
运行时类型生成器的循环引用类型创建问题
我正在用TypeBuilder构建运行时类型生成器,传入属性名称和类型列表生成类型的场景能正常运行,但现在要实现类似以下的循环引用结构:
public class Person { public string Name { get; set; } public Person Child { get; set; } }
现在卡在了“先有鸡还是先有蛋”的问题上——不知道怎么让TypeBuilder把还没创建出来的类型作为属性类型,同时想了解CLR是怎么处理这种情况的。
当前实现代码
FieldDescription类
public class FieldDescription { public string FieldName { get; set; } public Type FieldType { get; set; } }
CustomTypeBuilder类
public static class CustomTypeBuilder { public static Type CompileResultType(List<FieldDescription> fields, string TypeName) { TypeBuilder tb = GetTypeBuilder(TypeName); ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName); foreach (var field in fields) CreateProperty(tb, field); Type objectType = tb.CreateType(); return objectType; } private static TypeBuilder GetTypeBuilder(string typeSignature) { var an = new AssemblyName(typeSignature); AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run); ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MyModule"); TypeBuilder tb = moduleBuilder.DefineType(typeSignature, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout, null); FieldBuilder fieldBuilder = tb.DefineField("_ToStringValue", typeof(string), FieldAttributes.Private); return tb; } private static void CreateProperty(TypeBuilder tb, FieldDescription field) { string propertyName = field.FieldName; Type propertyType = field.FieldType ?? tb; FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual, propertyType, Type.EmptyTypes); ILGenerator getIl = getPropMthdBldr.GetILGenerator(); getIl.Emit(OpCodes.Ldarg_0); getIl.Emit(OpCodes.Ldfld, fieldBuilder); getIl.Emit(OpCodes.Ret); MethodBuilder setPropMthdBldr = tb.DefineMethod("set_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual, null, new[] { propertyType }); ILGenerator setIl = setPropMthdBldr.GetILGenerator(); Label modifyProperty = setIl.DefineLabel(); Label exitSet = setIl.DefineLabel(); setIl.MarkLabel(modifyProperty); setIl.Emit(OpCodes.Ldarg_0); setIl.Emit(OpCodes.Ldarg_1); setIl.Emit(OpCodes.Stfld, fieldBuilder); setIl.Emit(OpCodes.Nop); setIl.MarkLabel(exitSet); setIl.Emit(OpCodes.Ret); propertyBuilder.SetGetMethod(getPropMthdBldr); propertyBuilder.SetSetMethod(setPropMthdBldr); } }
解决循环引用类型创建的方法
其实TypeBuilder本身就支持这种场景——直接把TypeBuilder实例当作类型参数传入即可,不需要等CreateType()调用完成。因为TypeBuilder继承自Type,CLR在处理动态类型时,会在调用CreateType()时完成所有类型引用的绑定。
修改后的调用示例
要创建Person类这种循环引用类型,只需要在构建FieldDescription时,把Child字段的FieldType设为TypeBuilder实例本身(或留空,利用你代码里的field.FieldType ?? tb逻辑),然后正常调用CompileResultType即可:
// 构建字段描述 var fields = new List<FieldDescription> { new FieldDescription { FieldName = "Name", FieldType = typeof(string) }, // 循环引用字段留空,让代码自动使用当前TypeBuilder作为类型 new FieldDescription { FieldName = "Child", FieldType = null } }; // 生成类型 Type personType = CustomTypeBuilder.CompileResultType(fields, "Person");
你的代码里已经写了Type propertyType = field.FieldType ?? tb;,这部分逻辑是正确的,只要在传入字段描述时,把循环引用的字段类型留空,就会自动使用当前的TypeBuilder作为属性类型。
CLR如何处理这种情况
CLR在处理动态类型的循环引用时,采用了延迟绑定的机制:
- 当你用TypeBuilder定义字段、属性或方法时,CLR不会立即解析所有类型引用,而是记录下TypeBuilder的引用。
- 当你调用
TypeBuilder.CreateType()时,CLR会一次性完成所有类型的验证和绑定,包括处理循环引用的情况——此时TypeBuilder已经可以转化为正式的Type实例,所有指向它的引用都会被替换为实际的类型。
这种机制和编译静态代码时的处理逻辑类似:编译器在编译类时,会先记录类的存在,处理完所有类的定义后再完成交叉引用的绑定,CLR对动态类型的处理也是同理。
内容的提问来源于stack exchange,提问作者jekcom
相关产品推荐
相关产品推荐

