Используя C# 3.5 я пытаюсь генерировать динамические типы во времени выполнения с помощью отражения, испускают. Я использовал Динамический образец Библиотеки Запроса от Microsoft для создания генератора класса. Все работает, моя проблема состоит в том, что 100 сгенерированных типов расширяют использование памяти приблизительно на 25 МБ. Это - абсолютно недопустимый профиль памяти как в конечном счете, я хочу поддерживать наличие нескольких сотен тысяч типов, сгенерированных в памяти.
Профилирование памяти показывает, что память, по-видимому, сохранена различной Системой. Отражение. Испустите типы и методы, хотя я не могу выяснить почему. Я не нашел других, говорящих об этой проблеме, таким образом, я надеюсь кто-то в этом сообществе или знаю то, что я делаю неправильно или если это - ожидаемое поведение.
Изобретенный пример ниже:
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 dpl = new List(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, dp.Type, null);
MethodBuilder mbGet = tb.DefineMethod("get_" + dp.Name,
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
dp.Type, 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,
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
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",
MethodAttributes.Public | MethodAttributes.ReuseSlot |
MethodAttributes.Virtual | MethodAttributes.HideBySig,
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("get_Default"), null);
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Ldfld, field);
gen.EmitCall(OpCodes.Callvirt, 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'd. Я не могу вспомнить, какое поле и что оно содержало сейчас, но это можно увидеть из SOS в WinDbg.
Хорошая новость заключается в том, что .NET 4 поддерживает динамические сборки, поддерживающие сборку мусора:)
Независимо от реальной проблемы, с которой вы сталкиваетесь сейчас, я настоятельно рекомендую не использовать ваш текущий подход. Reflection.Emit не был разработан для поддержки создания сотен тысяч типов (например, см. this connect issue , хотя эта конкретная проблема может возникнуть только в том случае, если вы помещаете их все в одну динамическую сборку). Зачем вам нужно создавать столько типов?
Хорошо , первое, что я отмечаю, это то, что вы создаете новую фабрику и, следовательно, новый AssemblyBuilder
на каждой итерации. Можно ли повторно использовать фабрику (создавать несколько типов в одной динамической сборке)?
Похоже, что это реальная утечка памяти в System.Reflection.Emit. NEW SOLUTION BELOW Я смог избавиться от большей части использованной памяти, используя отражение и ручной процесс удаления. Я использовал методы расширения, чтобы добавить метод Dispose для некоторых типов. Это не очищает все, но код показывает, как это сделать. Я перехожу к другому способу получения нужного мне результата. Код для тех, кто интересуется, как это сделать, находится здесь.
В оригинальном примере вы бы вызвали tb.Dispose()
на вашем экземпляре TypeBuilder после того, как вы сгенерировали тип. Методы расширения приведены ниже, помните ЭТО НЕ ОЧИЩАЕТ ВСЕ, но позволяет освободить большую часть памяти. Этот код также не оптимизирован для скорости. Есть способы ускорить используемое отражение, это просто пример. Используйте на свой страх и риск.
public static void Dispose(this TypeBuilder tb)
{
if (tb == null)
return;
Type tbType = typeof(TypeBuilder);
FieldInfo tbMbList = tbType.GetField("m_listMethods", BindingFlags.Instance | BindingFlags.NonPublic); //List<MethodBuilder>
FieldInfo tbDecType = tbType.GetField("m_DeclaringType", BindingFlags.Instance | BindingFlags.NonPublic);//TypeBuilder
FieldInfo tbGenType = tbType.GetField("m_genTypeDef", BindingFlags.Instance | BindingFlags.NonPublic);//TypeBuilder
FieldInfo tbDeclMeth = tbType.GetField("m_declMeth", BindingFlags.Instance | BindingFlags.NonPublic);//MethodBuilder
FieldInfo tbMbCurMeth = tbType.GetField("m_currentMethod", BindingFlags.Instance | BindingFlags.NonPublic);//MethodBuilder
FieldInfo tbMod = tbType.GetField("m_module", BindingFlags.Instance | BindingFlags.NonPublic);//ModuleBuilder
FieldInfo tbGenTypeParArr = tbType.GetField("m_inst", BindingFlags.Instance | BindingFlags.NonPublic); //GenericTypeParameterBuilder[]
TypeBuilder tempDecType = tbDecType.GetValue(tb) as TypeBuilder;
tempDecType.Dispose();
tbDecType.SetValue(tb, null);
tempDecType = tbGenType.GetValue(tb) as TypeBuilder;
tempDecType.Dispose();
tbDecType.SetValue(tb, null);
MethodBuilder tempMeth = tbDeclMeth.GetValue(tb) as MethodBuilder;
tempMeth.Dispose();
tbDeclMeth.SetValue(tb,null);
tempMeth = tbMbCurMeth.GetValue(tb) as MethodBuilder;
tempMeth.Dispose();
tbMbCurMeth.SetValue(tb, null);
ArrayList mbList = tbMbList.GetValue(tb) as ArrayList;
for (int i = 0; i < mbList.Count; i++)
{
tempMeth = mbList[i] as MethodBuilder;
tempMeth.Dispose();
mbList[i] = null;
}
tbMbList.SetValue(tb, null);
ModuleBuilder tempMod = tbMod.GetValue(tb) as ModuleBuilder;
tempMod.Dispose();
tbMod.SetValue(tb, null);
tbGenTypeParArr.SetValue(tb, null);
}
public static void Dispose(this MethodBuilder mb)
{
if (mb == null)
return;
Type mbType = typeof(MethodBuilder);
FieldInfo mbILGen = mbType.GetField("m_ilGenerator", BindingFlags.Instance | BindingFlags.NonPublic);
//FieldInfo mbIAttr = mbType.GetField("m_iAttributes", BindingFlags.Instance | BindingFlags.NonPublic);
FieldInfo mbMod = mbType.GetField("m_module", BindingFlags.Instance | BindingFlags.NonPublic); //ModuleBuilder
FieldInfo mbContType = mbType.GetField("m_containingType", BindingFlags.Instance | BindingFlags.NonPublic);
FieldInfo mbLocSigHelp = mbType.GetField("m_localSignature", BindingFlags.Instance | BindingFlags.NonPublic);//SignatureHelper
FieldInfo mbSigHelp = mbType.GetField("m_signature", BindingFlags.Instance | BindingFlags.NonPublic);//SignatureHelper
ILGenerator tempIlGen = mbILGen.GetValue(mb) as ILGenerator;
tempIlGen.Dispose();
SignatureHelper tempmbSigHelp = mbLocSigHelp.GetValue(mb) as SignatureHelper;
tempmbSigHelp.Dispose();
tempmbSigHelp = mbSigHelp.GetValue(mb) as SignatureHelper;
tempmbSigHelp.Dispose();
ModuleBuilder tempMod = mbMod.GetValue(mb) as ModuleBuilder;
tempMod.Dispose();
mbMod.SetValue(mb, null);
mbILGen.SetValue(mb, null);
mbContType.SetValue(mb, null);
mbLocSigHelp.SetValue(mb, null);
mbSigHelp.SetValue(mb, null);
mbMod.SetValue(mb, null);
}
public static void Dispose(this SignatureHelper sh)
{
if (sh == null)
return;
Type shType = typeof(SignatureHelper);
FieldInfo shModule = shType.GetField("m_module", BindingFlags.Instance | BindingFlags.NonPublic);
//FieldInfo shSig = shType.GetField("m_signature", BindingFlags.Instance | BindingFlags.NonPublic);
shModule.SetValue(sh, null);
//shSig.SetValue(sh, null);
}
public static void Dispose(this ILGenerator ilGen)
{
if (ilGen == null)
return;
Type ilGenType = typeof(ILGenerator);
FieldInfo ilSigHelp = ilGenType.GetField("m_localSignature", BindingFlags.Instance | BindingFlags.NonPublic);//SignatureHelper
SignatureHelper sigTemp = ilSigHelp.GetValue(ilGen) as SignatureHelper;
sigTemp.Dispose();
ilSigHelp.SetValue(ilGen, null);
}
public static void Dispose(this ModuleBuilder modBuild)
{
if (modBuild == null)
return;
Type modBuildType = typeof(ModuleBuilder);
FieldInfo modBuildModData = modBuildType.GetField("m__moduleData", BindingFlags.Instance | BindingFlags.NonPublic |BindingFlags.FlattenHierarchy );
FieldInfo modTypeBuildList = modBuildType.GetField("m__TypeBuilderList", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
ArrayList modTypeList = modTypeBuildList.GetValue(modBuild) as ArrayList;
if(modTypeList != null)
{
for (int i = 0; i < modTypeList.Count; i++)
{
TypeBuilder tb = modTypeList[i] as TypeBuilder;
tb.Dispose();
modTypeList = null;
}
modTypeBuildList.SetValue(modBuild, null);
}
modBuildModData.SetValue(modBuild, null);
}
EDIT
Найдена фактическая причина: Похоже, что каждый тип, созданный в динамической сборке, хранит ссылку на ModuleBuilder
(в Type.Module
), который в свою очередь хранит список TypeBuilder
объектов. Этот список сканируется каждый раз при добавлении типа для проверки конфликтов имен. Если вы держите HashSet
вне процедуры генерации типов, чтобы гарантировать, что вы не получите никаких конфликтов имен, вы можете вызвать Clear на ModuleBuilder
частной переменной m__TypeBuilderList
после генерации Type
без каких-либо негативных побочных эффектов (пока)