0

The documentation states

You must release all objects that represent parts of the assembly. The ModuleBuilder that defines T keeps a reference to the TypeBuilder, and the AssemblyBuilder object keeps a reference to the ModuleBuilder, so references to these objects must be released. Even the existence of a LocalBuilder or an ILGenerator used in the construction of T prevents unloading.

To do so I have prepared some methods:

private static IEnumerable<MemberInfo> GetMemberInfo(object obj)
{
        if(obj == null)
        {
                return Enumerable.Empty<MemberInfo>();
        }

        var type = obj.GetType();
        if (type.IsPrimitive || type == typeof(string) || type == typeof(object))
        {
                return Enumerable.Empty<MemberInfo>();
        }            
        return GetMemberInfo(type);
}

private static IEnumerable<MemberInfo> GetMemberInfo(Type type)
{
        if (type.IsPrimitive || type == typeof(string) || type == typeof(object) || type == null)
        {
                return Enumerable.Empty<MemberInfo>();
        }

        var memberInfos = type.GetMembers(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
                                        .Where(mi => mi.MemberType == MemberTypes.Property || mi.MemberType == MemberTypes.Field);

        memberInfos.Concat(
                memberInfos.OfType<PropertyInfo>()
                        .Select(pi => type.GetField($"<{pi.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static))
                        .Where(x => x != null));

        return memberInfos;
}

public static void NullAllValues(MemberInfo memberInfo, object obj)
{
        if(obj == null)
        {
                return;
        }

        var objType = obj.GetType();
        if (objType.IsPrimitive || objType == typeof(string) || objType == typeof(object))
        {
                return;
        }

        FieldInfo fieldInfo = memberInfo as FieldInfo;
        if (fieldInfo != null)
        {
                if(fieldInfo.FieldType == typeof(string) ||fieldInfo.FieldType.IsPrimitive || fieldInfo.FieldType == typeof(object))
                {
                        return;
                }

                if (fieldInfo.FieldType.IsArray)
                {
                        if (fieldInfo.FieldType.GetElementType().IsPrimitive)
                        {
                                return;
                        }
                }                               
        }

        PropertyInfo propertyInfo = memberInfo as PropertyInfo;
        if (propertyInfo != null) {

                if (propertyInfo.PropertyType == typeof(string) || propertyInfo.PropertyType.IsPrimitive || propertyInfo.PropertyType == typeof(object))
                {
                        return;
                }

                if (propertyInfo.PropertyType.IsArray)
                {
                        if (propertyInfo.PropertyType.GetElementType().IsPrimitive)
                        {
                                return;
                        }
                }                
        }

        if (fieldInfo != null)
        {   
                try
                {
                        var value = fieldInfo.GetValue(obj);

                        if (value == null)
                        {
                                return;
                        }

                        if (fieldInfo.FieldType.IsArray)
                        {
                                object[] array = (object[])fieldInfo.GetValue(obj);

                                if (array == null)
                                {
                                        return;
                                }

                                foreach (var index in Enumerable.Range(0, array.Length))
                                {
                                        foreach (var mi in GetMemberInfo(obj: array[index]))
                                        {
                                                NullAllValues(mi, array[index]);
                                        }
                                }

                                return;
                        }


                        fieldInfo.SetValue(obj, null, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.SetField | BindingFlags.Public | BindingFlags.Static, null, null);
                        return;
                }

                catch
                {
                        return;
                }
        }

        if (propertyInfo != null)
        {   
                try
                {
                        if (propertyInfo.PropertyType.IsArray)
                        {
                                object[] array = (object[])propertyInfo.GetValue(obj);

                                if (array == null)
                                {
                                        return;
                                }

                                foreach (var index in Enumerable.Range(0, array.Length))
                                {
                                        foreach (var mi in GetMemberInfo(obj: array[index]))
                                        {
                                                NullAllValues(mi, array[index]);
                                        }
                                }

                                return;
                        }

                        var value = propertyInfo.GetValue(obj);

                        if (value == null)
                        {
                                return;
                        }

                        propertyInfo.SetValue(obj, null, BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, null, null);
                        return;
                }
                catch
                {
                        return;
                }
        }
}

It could be said it works. At least until there is created type by TypeBuilder. This is because of no setters available on Type and no option to null backing fields (reflection does not find them).

static void Main(string[] args)
{
        const string collectibleOne = "CollectibleOne";
        const string someMethod = "SomeMethod";

        var collAssem = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(collectibleOne), AssemblyBuilderAccess.RunAndCollect);

        ModuleBuilder moduleBuilder = collAssem.DefineDynamicModule("SomeModule");
        TypeBuilder typeBuilder = moduleBuilder.DefineType("EventType", TypeAttributes.Class | TypeAttributes.Public);

        MethodBuilder methodBuilder = typeBuilder.DefineMethod(someMethod, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HasSecurity, CallingConventions.Standard, typeof(void), new[] { typeof(object), typeof(EventArgs) });
        methodBuilder.CreateMethodBody(new byte[50], 50);

        //Type type = typeBuilder.CreateType();           

        foreach (var mi in GetMemberInfo(obj: methodBuilder))
        {
                NullAllValues(mi, methodBuilder);
        }
        methodBuilder = null;

        foreach (var mi in GetMemberInfo(obj: typeBuilder))
        {
                NullAllValues(mi, typeBuilder);
        }
        typeBuilder = null;

        foreach (var mi in GetMemberInfo(obj: moduleBuilder))
        {
                NullAllValues(mi, moduleBuilder);
        }
        moduleBuilder = null;

        //foreach (var mi in GetMemberInfo(obj: type))
        //{
        //    NullAllValues(mi, type);
        //}
        //type = null;

        foreach (var mi in GetMemberInfo(obj: collAssem))
        {
                NullAllValues(mi, collAssem);
        }
        collAssem = null;

        GC.WaitForPendingFinalizers();
        GC.Collect();

        foreach (var assem in AppDomain.CurrentDomain.GetAssemblies())
        {
                if (assem.GetName().Name == collectibleOne)
                {   
                        throw new Exception("Found!");
                }
        }
}

If you uncomment the code you will get exception. No option to get backing fields with no option to set props without setters mean the type cannot be nulled so it will retain some references to assembly that could not be collected.

My questions:

  • Am I wrong?
  • If so how to achieve this?
  • Is the documentation wrong?
  • If so how the collectable assembly can be collected?

If you are in doubt you can try to comment out foreachs calling the NullAllValues. You will get exception too.

There is also thread about this on Git

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.