1

[PURPOSE]

I need to use reflections to

  1. Load an assembly into a separate ApplicationLoadContext
  2. Create a type from it
  3. Create an instance from the type
  4. Invoke functions on the instance repeatedly
  5. After some time, perhaps days, when this assembly is no longer needed, unload the ApplicationLoadContext then delete the assembly file.

If you want to know why I am doing this, please see skybridge.net.au.

[The problem]

Although I have set all references to the assembly, type and instances to null before unloading the ApplicationLoadContext, it still failed to unload.

[Code demo 1]

The following code shows how my code works. It failed to delete the DLL file:

namespace MyDll
{
    public class MyText
    {
        public string m_text;

        public MyText(string text)
        {
            m_text = text;
        }

        public string GetText()
        {
            return m_text;
        }
    }

    public class MyClass
    {
        public string GetTextByBytes(byte[] bytes)
        {
            string json = Encoding.UTF8.GetString(bytes);
            var obj = JsonConvert.DeserializeObject<MyText>(json);
            return obj.GetText();
        }

        public string GetTextByObject(MyText text)
        {
            return text.GetText();
        }

        public string GetGuidText(Guid guid)
        {
            return guid.ToString();
        }

        public string GetText(string str)
        {
            return str;
        }

        public List<string> GetMultipleTexts(List<string> lst)
        {
            return lst;
        }

    }
}

    static void Main(string[] args)
    {
        var ctx = new AssemblyLoadContext(null, isCollectible: true);

        var assembly = ctx.LoadFromAssemblyPath(dllFilePath);
        var myType = assembly.GetType("MyDll.MyClass");
        var instance = Activator.CreateInstance(myType, new object[] { }, null);
        var myMethod = myType.GetMethod("GetGuidText");
        var res = myMethod.Invoke(instance, new object[] { Guid.NewGuid() });

        assembly = null;
        myType = null;
        instance = null;
        myMethod = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Thread.Sleep(2000);
        GC.Collect();
        GC.WaitForPendingFinalizers();

        ctx.Unload();

        GC.Collect();
        GC.WaitForPendingFinalizers();
        Thread.Sleep(2000);
        GC.Collect();
        GC.WaitForPendingFinalizers();

        File.Delete(dllFilePath);          
        Console.WriteLine($"Done!");
    }

[Code demo 2]

But, if I put the code in a separate function, Test(), the deletion works:

    private static void Test(AssemblyLoadContext ctx)
    {
        var assembly = ctx.LoadFromAssemblyPath(dllFilePath);
        var myType = assembly.GetType("MyDll.MyClass");
        var instance = Activator.CreateInstance(myType, new object[] { }, null);
        var myMethod = myType.GetMethod("GetGuidText");
        var res = myMethod.Invoke(instance, new object[] { Guid.NewGuid() });
    }

    static void Main(string[] args)
    {
        var ctx = new AssemblyLoadContext(null, isCollectible: true);
        Test(ctx);
        ctx.Unload();

        GC.Collect();
        GC.WaitForPendingFinalizers();
        Thread.Sleep(2000);
        GC.Collect();
        GC.WaitForPendingFinalizers();

        File.Delete(dllFilePath);         
        Console.WriteLine($"Done!");
    }

[But I can't change my code to the demo 2 style]

In my SkyBridge app, the loaded assembly and the types and instances loaded from this assembly are placed into a global collection, to be invoked by a separate thread. So I can't put them into a function as local variables and let them go out of scope when that function exits, like in [Code demo 2].

Instead, when an assembly is no longer needed, all I can do is like [Code demo 1], to remove them from the global collection, set their references to null, and hope GC can collect them so I can unload and delete. But it always fails.

[Final Question]

Is there anything that I can do in [Code demo 1] to enable the unloading and deletion, without putting the types and instances into a function as local variables?

********* Added on 2023-06-02 **********

Now the problem becomes clearer. If a local variable is declared in a function, when that function ends, that variable goes out of scope. When I call GC.Collect, it is immediately collected. However, if this variable is a global variable that never goes out of scope, even if I set it to null then call GC.Collect, it is not collected.

Is there a way to force a global variable that never goes out of scope to be immediately collected after GC.Collect?

  • why dont you implement IDisposable at class level ? – jmvcollaborator May 31 '23 at 23:56
  • I did. But in the Dispose method all I can do is to set the references to the assembly and types to null. They are not disposable. It still doesn't work because the code is still like [code demo 1]. The classes are all global, not a local variable in a method that goes out of scope. –  Jun 01 '23 at 00:15
  • since i found this really interesting please tell me why first approach i replicated with your code and it worked? i mean no errors with your snippet – jmvcollaborator Jun 01 '23 at 01:17
  • 3
    https://stackoverflow.com/questions/17130382/understanding-garbage-collection-in-net – Hans Passant Jun 01 '23 at 06:43
  • 1
    Does this answer your question? [Understanding garbage collector behavior for a local variable](https://stackoverflow.com/questions/22495819/understanding-garbage-collector-behavior-for-a-local-variable) – Charlieface Jun 01 '23 at 13:03
  • 1
    You need to put the code that uses those variables into a separate function if you are in Debug mode – Charlieface Jun 01 '23 at 13:04
  • jmvcollaborator: please read the introduction of my website skybridge.net.au to see why I am doing this. [Code demo 1] kept failing on my end. –  Jun 02 '23 at 02:09
  • Charlieface: I tried in release mode and the behavior is still the same: setting a local variable to null does not cause it to be garbage collected. –  Jun 02 '23 at 02:55

0 Answers0