如何在 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 Framework 版本。
- 我的 Prism 版本是 6.1.0
- 我将使用 userControlTypes 向最终用户显示应用程序中的所有 UserControl 类型。
- IUserControl 界面什么都不包含。
此行为是设计使然。 .net CLR 不会加载到程序集中,除非 called/entered 强制加载它。想象一下 运行 如果目录中的每个 .dll 文件都在应用程序启动时加载到内存中而不是在 运行 第一次引用某个类型时加载到内存中,那么想象一下应用程序的启动成本,一些具有大型库的应用程序将有几分钟的加载时间(甚至更多?)。这也不现实,因为某些类型被解析为执行文件夹之外的库,例如解析为 GAC 的程序集。
在您的第一个示例中,AppDomain.CurrentDomain.GetAssemblies
只会 return 该应用程序域中加载的程序集,而不是所有程序集。要查看此内容,您可以添加一个 {typeof(ViziteUserControl)}
( 取自您的下一个代码部分 )并将其放在它的正上方,这将强制加载类型(和包含程序集)由 CLR 现在它( 类型包含程序集 )也将被 return 编辑 AppDomain.CurrentDomain.GetAssemblies
。
在您的下一个代码片段中,您的代码明确地进入这些程序集并添加类型。我认为这不需要任何解释。
因此,如果您希望 AppDomain.CurrentDomain.GetAssemblies
在您的应用程序中加载所有类型,您需要强制程序集加载到内存中(如果它尚未这样做)。根据您的结构,您可以通过几种方式执行此操作。
- 遍历磁盘上的 .dll 文件(使用像
Assembly.GetExecutingAssembly.Location
这样的参考位置)并调用 Assembly.LoadFrom。使用通配符确保您只加载您的程序集,而不是您遇到的每个 .dll 库。
- 在配置文件中引用感兴趣的类型并从那里加载它们。从配置字符串列表创建类型列表时,您可以使用
Type t = Type.GetType(yourConfigType);
。
- 在配置文件中引用感兴趣的程序集并以与选项 1 相同的方式加载到 DLL 中。
- 只需像在上一个示例中那样对列表进行硬编码。
如果您选择选项 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 会找到的程序集。
我正在使用 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 Framework 版本。
- 我的 Prism 版本是 6.1.0
- 我将使用 userControlTypes 向最终用户显示应用程序中的所有 UserControl 类型。
- IUserControl 界面什么都不包含。
此行为是设计使然。 .net CLR 不会加载到程序集中,除非 called/entered 强制加载它。想象一下 运行 如果目录中的每个 .dll 文件都在应用程序启动时加载到内存中而不是在 运行 第一次引用某个类型时加载到内存中,那么想象一下应用程序的启动成本,一些具有大型库的应用程序将有几分钟的加载时间(甚至更多?)。这也不现实,因为某些类型被解析为执行文件夹之外的库,例如解析为 GAC 的程序集。
在您的第一个示例中,AppDomain.CurrentDomain.GetAssemblies
只会 return 该应用程序域中加载的程序集,而不是所有程序集。要查看此内容,您可以添加一个 {typeof(ViziteUserControl)}
( 取自您的下一个代码部分 )并将其放在它的正上方,这将强制加载类型(和包含程序集)由 CLR 现在它( 类型包含程序集 )也将被 return 编辑 AppDomain.CurrentDomain.GetAssemblies
。
在您的下一个代码片段中,您的代码明确地进入这些程序集并添加类型。我认为这不需要任何解释。
因此,如果您希望 AppDomain.CurrentDomain.GetAssemblies
在您的应用程序中加载所有类型,您需要强制程序集加载到内存中(如果它尚未这样做)。根据您的结构,您可以通过几种方式执行此操作。
- 遍历磁盘上的 .dll 文件(使用像
Assembly.GetExecutingAssembly.Location
这样的参考位置)并调用 Assembly.LoadFrom。使用通配符确保您只加载您的程序集,而不是您遇到的每个 .dll 库。 - 在配置文件中引用感兴趣的类型并从那里加载它们。从配置字符串列表创建类型列表时,您可以使用
Type t = Type.GetType(yourConfigType);
。 - 在配置文件中引用感兴趣的程序集并以与选项 1 相同的方式加载到 DLL 中。
- 只需像在上一个示例中那样对列表进行硬编码。
如果您选择选项 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 会找到的程序集。