Search code examples
c#.netgarbage-collectionactivator

Lifespan of C# Assembly object instantiated by interface with Activator.CreateInstance


I'm experimenting with a plugin architecture using C# and dotnet. What I've discovered is that any instance created this way doesn't get collected until the application closes.

My original thought was to load each object and call a registration method, then allow it to die. The registration method would pass back the information needed to know when it should be loaded in the future and create a new instance at that time. This would prevent me from keeping 50 or so objects alive when not in use.

Putting a simple System.Diagnostics.Debug.Writeline in the constructor and finalizer of the classes this first loop through does not destroy any objects. Only when I close the application do any finalizer calls happen. If I do an experiment where I have an internal IProduct instance and load several instances to that I get 5 constructor calls and once I close 5 finalizer calls.

For the below I created 2 different DLL's with 1 class each implementing the interface. So I get a Class1 Constructor call and a Class2 Constructor call. But only get destructor calls when closing.

My initial expectations where that the finalizer would be called once the IProduct myDLL fell out of scope (iterating through/leaving the for loop) Or worst case the function itself. This is pared down to have no data pass from the loaded dll to the main program to ensure I'm not keeping anything alive through hanging onto a reference, but I'm still seeing the same results.

    void LoadProductPlugins()
    {
        string[] sFiles = Directory.GetFiles(Directory.GetCurrentDirectory() + "\\dll", "*.dll", SearchOption.TopDirectoryOnly);

        foreach (string strDll in sFiles)
        {
            Assembly assy = Assembly.LoadFile(strDll);

            Type[] types = (from t in assy.GetExportedTypes()
                                 where !t.IsInterface && !t.IsAbstract
                                 where typeof(IProduct).IsAssignableFrom(t)
                                 select t).ToArray();
            for (int i = 0; i < types.Length; i++)
            {
                IProduct myDLL = (IProduct) Activator.CreateInstance(types[i]);
            }
        }
    }

Solution

  • There is no difference in lifespan management of an object whether it is created via regular new, CreateInstance or any other method one can come up with. An object will be collected when GC is needed and the particular object is no longer reachable.

    Notes:

    • Finalizers are called only as part of full GC pass (Gen 2). This leads to behavior you see when finalizers are called at shutdown only because there is not enough memory usage to need GC earlier.
    • Assemblies themselves are not unloaded till they AppDomain is unloaded. Unless you are creating separate AppDomain for your plugins it means assembly will not be unloaded. You may want to read on assembly loading/versioning - Reading all posts around https://learn.microsoft.com/en-us/archive/blogs/suzcook/assembly-identity would save you a lot of time.