C# · 12月 31, 2021

c# – Reflect.Emit动态类型内存溢出

使用C#3.5我试图在运行时使用反射发射生成动态类型.我使用了Microsoft的 Dynamic Query Library样本来创建一个类生成器.一切正常,我的问题是100个生成的类型使内存使用量大约25MB.这是一个完全不可接受的内存配置文件,最终我想支持在内存中生成数十万种类型.

内存分析显示内存显然被各种System.Reflection.Emit类型和方法占据,尽管我无法弄清楚为什么.我没有找到其他人谈论这个问题,所以我希望这个社区的某个人知道我做错了什么,或者这是预期的行为.

以下示例:

using System;using System.Collections.Generic;using System.Text;using System.Reflection;using System.Reflection.Emit;namespace SmallRelfectExample{ class Program { static void Main(string[] args) { int typeCount = 100; int propCount = 100; Random rand = new Random(); Type dynType = null; SlimClassFactory scf = new SlimClassFactory(); for (int i = 0; i < typeCount; i++) { List<DynamicProperty> dpl = new List<DynamicProperty>(propCount); for (int j = 0; j < propCount; j++) { dpl.Add(new DynamicProperty(“Key” + rand.Next().ToString(),typeof(String))); } dynType = scf.CreateDynamicClass(dpl.ToArray(),i); //Optionally do something with the type here } Console.WriteLine(“SmallRelfectExample: {0} Types generated.”,typeCount); Console.ReadLine(); } } public class SlimClassFactory { private readonly ModuleBuilder module; public SlimClassFactory() { AssemblyName name = new AssemblyName(“DynamicClasses”); AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name,AssemblyBuilderAccess.Run); module = assembly.DefineDynamicModule(“Module”); } public Type CreateDynamicClass(DynamicProperty[] properties,int Id) { string typeName = “DynamicClass” + Id.ToString(); TypeBuilder tb = module.DefineType(typeName,TypeAttributes.Class | TypeAttributes.Public,typeof(DynamicClass)); FieldInfo[] fields = GenerateProperties(tb,properties); GenerateEquals(tb,fields); GenerateGetHashCode(tb,fields); Type result = tb.CreateType(); return result; } static FieldInfo[] GenerateProperties(TypeBuilder tb,DynamicProperty[] properties) { FieldInfo[] fields = new FieldBuilder[properties.Length]; for (int i = 0; i < properties.Length; i++) { DynamicProperty dp = properties[i]; FieldBuilder fb = tb.DefineField(“_” + dp.Name,dp.Type,FieldAttributes.Private); PropertyBuilder pb = tb.DefineProperty(dp.Name,PropertyAttributes.HasDefault,null); MethodBuilder mbGet = tb.DefineMethod(“get_” + dp.Name,MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,Type.EmptyTypes); ILGenerator genGet = mbGet.GetILGenerator(); genGet.Emit(OpCodes.Ldarg_0); genGet.Emit(OpCodes.Ldfld,fb); genGet.Emit(OpCodes.Ret); MethodBuilder mbSet = tb.DefineMethod(“set_” + dp.Name,null,new Type[] { dp.Type }); ILGenerator genSet = mbSet.GetILGenerator(); genSet.Emit(OpCodes.Ldarg_0); genSet.Emit(OpCodes.Ldarg_1); genSet.Emit(OpCodes.Stfld,fb); genSet.Emit(OpCodes.Ret); pb.Setgetmethod(mbGet); pb.SetSetMethod(mbSet); fields[i] = fb; } return fields; } static void GenerateEquals(TypeBuilder tb,FieldInfo[] fields) { MethodBuilder mb = tb.DefineMethod(“Equals”,MethodAttributes.Public | MethodAttributes.ReuseSlot | MethodAttributes.Virtual | MethodAttributes.HideBySig,typeof(bool),new Type[] { typeof(object) }); ILGenerator gen = mb.GetILGenerator(); LocalBuilder other = gen.DeclareLocal(tb); Label next = gen.DefineLabel(); gen.Emit(OpCodes.Ldarg_1); gen.Emit(OpCodes.Isinst,tb); gen.Emit(OpCodes.Stloc,other); gen.Emit(OpCodes.Ldloc,other); gen.Emit(OpCodes.Brtrue_S,next); gen.Emit(OpCodes.Ldc_I4_0); gen.Emit(OpCodes.Ret); gen.MarkLabel(next); foreach (FieldInfo field in fields) { Type ft = field.FieldType; Type ct = typeof(EqualityComparer<>).MakeGenericType(ft); next = gen.DefineLabel(); gen.EmitCall(OpCodes.Call,ct.getmethod(“get_Default”),null); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld,field); gen.Emit(OpCodes.Ldloc,other); gen.Emit(OpCodes.Ldfld,field); gen.EmitCall(OpCodes.Callvirt,ct.getmethod(“Equals”,new Type[] { ft,ft }),null); gen.Emit(OpCodes.Brtrue_S,next); gen.Emit(OpCodes.Ldc_I4_0); gen.Emit(OpCodes.Ret); gen.MarkLabel(next); } gen.Emit(OpCodes.Ldc_I4_1); gen.Emit(OpCodes.Ret); } static void GenerateGetHashCode(TypeBuilder tb,FieldInfo[] fields) { MethodBuilder mb = tb.DefineMethod(“GetHashCode”,typeof(int),Type.EmptyTypes); ILGenerator gen = mb.GetILGenerator(); gen.Emit(OpCodes.Ldc_I4_0); foreach (FieldInfo field in fields) { Type ft = field.FieldType; Type ct = typeof(EqualityComparer<>).MakeGenericType(ft); gen.EmitCall(OpCodes.Call,ct.getmethod(“GetHashCode”,new Type[] { ft }),null); gen.Emit(OpCodes.Xor); } gen.Emit(OpCodes.Ret); } } public abstract class DynamicClass { public override string ToString() { PropertyInfo[] props = GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); StringBuilder sb = new StringBuilder(); sb.Append(“{“); for (int i = 0; i < props.Length; i++) { if (i > 0) sb.Append(“,”); sb.Append(props[i].Name); sb.Append(“=”); sb.Append(props[i].GetValue(this,null)); } sb.Append(“}”); return sb.ToString(); } } public class DynamicProperty { private readonly string name; private readonly Type type; public DynamicProperty(string name,Type type) { if (name == null) throw new ArgumentNullException(“name”); if (type == null) throw new ArgumentNullException(“type”); this.name = name; this.type = type; } public string Name { get { return name; } } public Type Type { get { return type; } } }}解决方法 不幸的是,在ModuleBuilder中有一个静态字段保存在内存中,这样永远不会得到GC.我不记得哪个字段和它现在包含什么,但是这可以从WinDbg中的SOS内部看到.

好消息是,.NET 4支持支持GC的动态程序集:)