简体   繁体   中英

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

I'm developing modular application using WPF and Prism.
All my UserControls have separate assemblies and implement IUserControl interface.
I would like to list all Types which implement IUserControl interface form a loaded module library in this way;

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

But I cannot see all UserControl types implementing IUserControl in userControlTypes list.
When I use the all classes that implements IUserControl in Bootstrapper.cs like in the following;

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

I can get all desired UserControls from the list just I wrote above(userControlTypes).
What is the reason behind this?

FYI:

  • All assemblies target the same .NET framework version.
  • My Prism version is 6.1.0
  • I will use userControlTypes to show all UserControl types inside the application to the end-user.
  • IUserControl interface contains nothing.

This behavior is by design. The .net CLR will not load in an assembly unless it is called/entered which forces it to be loaded. Imagine the startup cost of running an app if every .dll file in the directory were loaded into memory when the application started as opposed to when a type was referenced at run time for the first time, some apps with large libraries would have load times of minutes (maybe even more?). Also it would not be realistic because some types are resolved to libraries outside of the execution folder like assemblies that resolve to the GAC.

In your first example AppDomain.CurrentDomain.GetAssemblies will only return the loaded assemblies, not all the assemblies, in that application domain. To see this you could add a {typeof(ViziteUserControl)} ( taken from your next code part ) and place it right above it, this will force the type (and containing assembly) to be loaded by the CLR and now it ( types containing assembly ) too will be returned by AppDomain.CurrentDomain.GetAssemblies .

In your next code fragment your code is explicitly entering these assemblies and adding the types. I do not think this requires any explaining.

So if you want AppDomain.CurrentDomain.GetAssemblies to load all your types across your application you need to force the assembly to load into memory if it has not already done so. Depending on your structure you could do this a couple of ways.

  1. Iterate through the .dll files on disk (using a reference location like Assembly.GetExecutingAssembly.Location ) and call Assembly.LoadFrom . Use wild cards to ensure you are only loading your assemblies and not every .dll library you are encountering.
  2. Reference interested types in a configuration file and load them from there. You can use Type t = Type.GetType(yourConfigType); when creating your list of types from your configuration string list.
  3. Reference interested assemblies in a configuration file and load in the DLL in the same manner as option 1.
  4. Just hard code the list as you did in your last example.

If you choose option 1 or 3 you will have to check to make sure you have not already loaded the assembly in memory before you call Assembly.LoadFrom. You can do this by again checking what is already loaded with AppDomain.CurrentDomain.GetAssemblies().Any(x =>your search query) .

Also Note that once you load an assembly into your application domain you cannot unload it for the life of that application domain. If you do not want this but you still want to dynamically find all your types you will have to create a 2nd application domain to find all the types and return them as an array/list of fully qualified type name as a string. You can then unload this created application domain. Also, as correctly noted by @Peter below in the comments, use ReflectionOnlyLoadFrom if you go with this approach. This incurs much less overhead.

AppDomain.GetAssemblies() tells you the loaded assemblies, not the referenced ones. I can't speak to the Prism aspect of your question, and I agree with the comments that there is probably a better way to design this. But…

If you really want to enumerate all of the types that might get loaded in your AppDomain , you can approximate this by enumerating the types in the existing assemblies (ie as you've done here, with AppDomain.CurrentDomain.GetAssemblies() , but then for each assembly, call GetReferencedAssemblies() ), which returns an array of AssemblyName values that you can use to load additional assemblies. For each of those, you can in turn inspect all of their types (to find the implementors of IUserControl ) and to call GetReferencedAssemblies() to continue the recursive search.

Note that this still will not necessarily return all implementors of the IUserControl interface that your process might load. Assemblies can be loaded by means other than being referenced in your AppDomain 's assemblies, such as by code searching a directory for candidates, or even the user explicitly naming an assembly to load. This is why using mechanisms directly supported by whatever API you're using is a much better approach, to make sure that you find exactly those assemblies that that API would find.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM