如何在 AppDomain 中查找尚未加载的类型?

How to find a type that is not loaded yet in AppDomain?

我正在使用 WPF 和 Prism 开发模块化应用程序。
我所有的 UserControls 都有单独的程序集并实现 IUserControl 接口。
我想以这种方式列出所有实现 IUserControl 接口的类型形成加载的模块库;

//ModuleA.cs
var interfaceType = typeof(IUserControl);
var userControlTypes = AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(s => s.GetTypes())
            .Where(p => interfaceType.IsAssignableFrom(p) && p.IsClass);

但是我在 userControlTypes 列表中看不到所有实现 IUserControl 的 UserControl 类型。
当我使用在 Bootstrapper.cs 中实现 IUserControl 的所有 类 时,如下所示;

var userControlTypes = new List<Type>()
{ 
      {typeof(HastaKayitControl)},
      {typeof(ViziteUserControl)},
      {typeof(DenemeUserControl)},
      ...
};

我可以从上面写的列表中获取所有需要的 UserControls(userControlTypes)。
这背后的原因是什么?

仅供参考:

此行为是设计使然。 .net CLR 不会加载到程序集中,除非 called/entered 强制加载它。想象一下 运行 如果目录中的每个 .dll 文件都在应用程序启动时加载到内存中而不是在 运行 第一次引用某个类型时加载到内存中,那么想象一下应用程序的启动成本,一些具有大型库的应用程序将有几分钟的加载时间(甚至更多?)。这也不现实,因为某些类型被解析为执行文件夹之外的库,例如解析为 GAC 的程序集。

在您的第一个示例中,AppDomain.CurrentDomain.GetAssemblies 只会 return 该应用程序域中加载的程序集,而不是所有程序集。要查看此内容,您可以添加一个 {typeof(ViziteUserControl)} 取自您的下一个代码部分 )并将其放在它的正上方,这将强制加载类型(和包含程序集)由 CLR 现在它( 类型包含程序集 )也将被 return 编辑 AppDomain.CurrentDomain.GetAssemblies

在您的下一个代码片段中,您的代码明确地进入这些程序集并添加类型。我认为这不需要任何解释。

因此,如果您希望 AppDomain.CurrentDomain.GetAssemblies 在您的应用程序中加载所有类型,您需要强制程序集加载到内存中(如果它尚未这样做)。根据您的结构,您可以通过几种方式执行此操作。

  1. 遍历磁盘上的 .dll 文件(使用像 Assembly.GetExecutingAssembly.Location 这样的参考位置)并调用 Assembly.LoadFrom。使用通配符确保您只加载您的程序集,而不是您遇到的每个 .dll 库。
  2. 在配置文件中引用感兴趣的类型并从那里加载它们。从配置字符串列表创建类型列表时,您可以使用 Type t = Type.GetType(yourConfigType);
  3. 在配置文件中引用感兴趣的程序集并以与选项 1 相同的方式加载到 DLL 中。
  4. 只需像在上一个示例中那样对列表进行硬编码。

如果您选择选项 1 或 3,则必须检查以确保在调用 Assembly.LoadFrom 之前尚未将程序集加载到内存中。您可以通过使用 AppDomain.CurrentDomain.GetAssemblies().Any(x =>your search query) 再次检查已加载的内容来执行此操作。

另请注意,一旦将程序集加载到应用程序域中,就无法在该应用程序域的生命周期内卸载它。如果你不想要这个,但你仍然想动态地找到你所有的类型,你将不得不创建一个第二个应用程序域来找到所有的类型和 return 它们作为一个 array/list 的完全限定类型名称作为一个字符串。然后您可以卸载这个创建的应用程序域。此外,正如下面评论中@Peter 正确 指出的那样,如果您采用这种方法,请使用 ReflectionOnlyLoadFrom。这会产生更少的开销。

AppDomain.GetAssemblies() 告诉您 loaded 程序集,而不是引用的程序集。我无法谈论您问题的 Prism 方面,我同意评论,即可能有更好的设计方法。但是……

如果您真的想枚举 可能 加载到您的 AppDomain 中的所有类型,您可以通过枚举现有程序集中的类型(即,正如您在此处所做的那样,使用 AppDomain.CurrentDomain.GetAssemblies(),但随后对于每个程序集,调用 GetReferencedAssemblies()),其中 return 是一个 AssemblyName 值数组,您可以使用加载额外的程序集。对于其中的每一个,您可以依次检查它们的所有类型(以找到 IUserControl 的实现者)并调用 GetReferencedAssemblies() 继续递归搜索。

请注意,此 still 不一定 return all IUserControl 接口的实现者,您的进程可能会加载。可以通过 AppDomain 的程序集中引用以外的方式加载程序集,例如通过代码搜索候选目录,甚至用户明确命名要加载的程序集。这就是为什么使用您正在使用的 API 直接支持的机制是一种更好的方法,以确保您准确找到那些 API 会找到的程序集。