I was writing some reflection code the other day where I wanted to search through all of my assemblies for a particular interface, and call a method on it at startup. Seemed pretty simple but in reality there is no clear, easy, one size fits all way to get assemblies. This post may be a bit dry for some, but if I help just one person from banging their head against the wall with this stuff, then it’s worth it!
I’m not really going to say “Use this one”, because of the ways to get assemblies, it’s highly likely only one way will work for your particular project so it’s pointless trying to lean towards one or the other. Simply try them all and see which one makes the most sense!
Using AppDomain.GetAssemblies
So the first option you might come across is AppDomain.GetAssemblies. It (seemingly) loads all Assemblies in the AppDomain which is to say essentially every Assembly that is used in your project. But there is a massive caveat. Assemblies in .NET are lazy loaded into the AppDomain. It doesn’t all at once load every assembly possible, instead it waits for you to make a call to a method/class in that assembly, and then loads it up – e.g. Loads it Just In Time. Makes sense because there’s no point loading an assembly up if you never use it.
But the problem is that at the point of you call AppDomain.GetAssemblies(), if you have not made a call into a particular assembly, it will not be loaded! Now if you are getting all assemblies for a startup method, it’s highly likely you wouldn’t have called into that assembly yet, meaning it’s not loaded into the AppDomain!
Or in code form :
AppDomain.CurrentDomain.GetAssemblies(); // Does not return SomeAssembly as it hasn't been called yet. SomeAssembly.SomeClass.SomeMethod(); AppDomain.CurrentDomain.GetAssemblies(); // Will now return SomeAssembly.
So while this might look like an attractive option, just know that timing is everything with this method.
Using The AssemblyLoad Event
Because you can’t be sure when you call CurrentDomain.GetAssemblies() that everything is loaded, there is actually an event that will run when the AppDomain loads another Assembly. Basically, when an assembly is lazy loaded, you can be notified. It looks like so :
AppDomain.CurrentDomain.AssemblyLoad += (sender, args) => { var assembly = args.LoadedAssembly; };
This might be a solution if you just want to check something when Assemblies are loaded, but that process doesn’t necessarily have to happen at a certain point in time (e.g. Does not have to happen within the Startup.cs of your .NET Core app).
The other problem with this is that you can’t be sure that by the time you’ve added your event handler, that assemblies haven’t already been loaded (Infact they most certainly would have). So what then? You would need to duplicate the effort by first adding your event handler, then immediately after checking AppDomain.CurrentDomain.GetAssemblies for things that have already been loaded.
It’s a niche solution, but it does work if you are fine with doing something with the lazy loaded assemblies.
Using GetReferencedAssemblies()
Next cab off the rank is GetReferencedAssemblies(). Essentially you can take an assembly, such as your entry assembly which is typically your web project, and you find all referenced assemblies. The code itself looks like this :
Assembly.GetEntryAssembly().GetReferencedAssemblies();
Again, looks to do the trick but there is another big problem with this method. In many projects you have a separation of concerns somewhere along the lines of say Web Project => Service Project => Data Project. The Web Project itself doesn’t reference the Data Project directly. Now when you call “GetReferencedAssemblies” it means direct references. Therefore if you’re looking to also get your Data Project in the assembly list, you are out of luck!
So again, may work in some cases, but not a one size fits all solution.
Looping Through GetReferencedAssemblies()
You’re other option for using GetReferencedAssemblies() is actually to create a method that will loop through all assemblies. Something like this :
public static List GetAssemblies() { var returnAssemblies = new List(); var loadedAssemblies = new HashSet(); var assembliesToCheck = new Queue(); assembliesToCheck.Enqueue(Assembly.GetEntryAssembly()); while(assembliesToCheck.Any()) { var assemblyToCheck = assembliesToCheck.Dequeue(); foreach(var reference in assemblyToCheck.GetReferencedAssemblies()) { if(!loadedAssemblies.Contains(reference.FullName)) { var assembly = Assembly.Load(reference); assembliesToCheck.Enqueue(assembly); loadedAssemblies.Add(reference.FullName); returnAssemblies.Add(assembly); } } } return returnAssemblies; }
Rough around the edges but it does work and means that on startup, you can instantly view all assemblies.
The one time you might get stuck with this is if you are loading assemblies dynamically and so they aren’t actually referenced by any project. For that, you’ll need the next method.
Directory DLL Load
A really rough way to get all solution DLLs is actually to load them out of your bin folder. Something like :
public static Assembly[] GetSolutionAssemblies() { var assemblies = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll") .Select(x => Assembly.Load(AssemblyName.GetAssemblyName(x))); return assemblies.ToArray(); }
It works but hoooo boy it’s a rough one. But the one big boon to possibly using this method is that a dll simply has to be in the directory to be loaded. So if you are dynamically loading DLLs for any reason, this is probably the only method that will work for you (Except maybe listening on the AppDomain for AssemblyLoad).
This is one of those things that looks like a hacktastic way of doing things, but you actually might be backed into the corner and this is the only way to solve it.
Getting Only “My” Assemblies
Using any of these methods, you’ll quickly find you are loading every Assembly under the sun into your project, including Nuget packages, .NET Core libraries and even runtime specific DLLs. In the .NET world, an Assembly is an Assembly. There is no concept of “Yeah but this one is my Assembly” and should be special.
The only way to filter things out is to check the name. You can either do it as a whitelist, so if all of your projects in your solution start with the word “MySolution.”, then you can do a filter like so :
Assembly.GetEntryAssembly().GetReferencedAssemblies().Where(x => x.Name.StartsWith("MySolution."))
Or instead you can go for a blacklist option which doesn’t really limit things to just your Assemblies, but at the very least cuts down on the number of Assemblies you are loading/checking/processing etc. Something like :
Assembly.GetEntryAssembly().GetReferencedAssemblies() .Where(x => !x.Name.StartsWith("Microsoft.") && !x.Name.StartsWith("System."))
Blacklisting may look stupid but in some cases if you are building a library that you actually don’t know the end solutions name, it’s the only way you can cut down on what you are attempting to load.
So, basically we must eager load all assemblies.
My question is, once you load all DLL files, do they get loaded once again when first used? We end up double loading them, right?
The first question must be why do you need to get assemblies.
Once you have the answer to this question, you will be able to choose the way to do it.
ie: if you need to load plugins, you should have a plugin folder or a naming convention.
This might be an ord article, but, relying on GetReferencedAssemblies will always fail if some class isnt referenced. Looping through assemblies, as suggested, will fail immediately, if the “first level reference” isnt used. So:
ReferenceA –> Reference B –> ReferenceC
If you try to do GetReferencedAssemblies(), starting with the GetEntryAssembly(), and there is no usage of ReferenceA, then the chain is broken, and all hope is lost.
This was incredibly useful. Thank you for posting it!
Thank you for this article. This was informative. I’m curious about the performance ramifications of these options. I’m also curious why you consider loading dlls from a folder to be a “hacky” way to do something. In some cases I can see this being a very logical way to do this. For instance, I am working on a solution that will include different job types to be ran by the Quartz.Net scheduler. I want to be able to extend this solution easily later by simply creating a new dll with new job types in them and uploading the dll to the solution. In my case if I can drop a dll into the correct folder and then trigger the application to re-scan the dlls, it would easily allow me to extend the program later without even having to touch the base code.
I’m just curious if there is a reason this is a bad idea (aside from the obvious make sure you can only add trusted dlls issue)?
In your case, you have a legitimate benefit to being able to drop files in a location.
In most cases however, reading files/directories to get a type that may already be loaded into memory feels a bit weird. Maybe that’s just me.