通过接口实例化的 C# 程序集对象的生命周期 Activator.CreateInstance

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

我正在试验使用 C# 和 dotnet 的插件架构。我发现,以这种方式创建的任何实例在应用程序关闭之前都不会被收集。

我最初的想法是加载每个对象并调用一个注册方法,然后让它消亡。注册方法将传回所需的信息,以了解将来何时加载它并在那时创建一个新实例。这会阻止我在不使用时让 50 个左右的对象保持活动状态。

在第一个循环中 classes 的构造函数和终结器中放置一个简单的 System.Diagnostics.Debug.Writeline 不会破坏任何对象。只有当我关闭应用程序时,才会发生任何终结器调用。如果我做一个实验,我有一个内部 IProduct 实例并加载几个实例,我得到 5 个构造函数调用,一旦我关闭 5 个终结器调用。

对于下面的内容,我创建了 2 个不同的 DLL,其中 1 个 class 每个都实现了接口。所以我得到了 Class1 Constructor 调用和 Class2 Constructor 调用。但是只有在关闭时才会调用析构函数。

我最初的期望是,一旦 IPproduct myDLL 超出范围(迭代 through/leaving for 循环)或最坏的情况是函数本身,就会调用终结器。这被缩减为没有数据从加载的 dll 传递到主程序,以确保我没有通过挂在引用上保持任何活动,但我仍然看到相同的结果。

    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]);
            }
        }
    }

无论对象是通过常规 newCreateInstance 还是任何其他可以想到的方法创建的,对象的生命周期管理都没有区别。当需要 GC 并且特定对象不再可访问时,将收集对象。

备注:

  • 终结器仅作为完整 GC 传递(第 2 代)的一部分被调用。这会导致您在关闭时调用终结器时看到的行为,只是因为没有足够的内存使用率需要更早的 GC。
  • 在卸载 AppDomain 之前,程序集本身不会被卸载。除非您为插件创建单独的 AppDomain,否则程序集不会被卸载。您可能想阅读有关集会 loading/versioning 的文章 - 阅读 https://docs.microsoft.com/en-us/archive/blogs/suzcook/assembly-identity 周围的所有帖子将为您节省大量时间。