📜 ⬆️ ⬇️

Dynamic loading, operation and unloading of assemblies in .NET

Quite often, the developer is faced with the question of expanding the basic algorithm with single-type tasks. For example, aggregators of various services that provide a single interface to the user, making requests to hundreds of other service providers. The task is in such a way that the main core can dynamically load assemblies with different implementations of some interface. No back-up work for a .NET programmer is not expected here initially. If the term "reflection" you know, you probably already want to pass by? But this topic is not about reflection as such ... I will tell you how to do it the most "pure". Those. with one caveat - the investigated assembly must be unloaded after their operation.


So the task: you need to create a console application, ExtensionLoader, which, when started, dynamically loads from its directory all the libraries in which the class that implements the IExtension interface was found:
interface IExtension { String GetExtensionName(); } 

After loading the found libraries, the application creates an instance for each found class (with IExtension) and executes a single method GetExtensionName (), the result displays in the console.

It sounds like a test task - in fact it was so ...
')
One nuance in this task was very interesting: The application architecture must be built in such a way that it is possible to unload all loadable libraries. This is where the fun begins. Quick "googling" did not give ready-made solutions. The analysis of the problem that has arisen took some time and I want to share it ... hoping to get a constructive response, and to avoid lying in vain ...

Let's start with a common library with a description of the interface. It will be a separate project by itself, so that it can be connected both to the extension and to the system core without catching all the implementations of this interface. This is sort of obvious. Our library, by assignment, will contain one interface. He has already been described in the assignment, so I will not repeat.

Next, we implement the IExtension interface. It is also obvious that this will be a separate project, which in our example will contain one implementation of the specified interface.
  public class Extension1 : MarshalByRefObject, IExtension { public Extension1() { } public string GetExtensionName() { return "Extension 1 from " + AppDomain.CurrentDomain.FriendlyName; } } 


The inquisitive reader has already noticed some excesses, which he did not expect to see here. Namely inheritance from MarshalByRefObject. The chip here is as follows: If we do not inherit from MarshalByRefObject, when we try to execute a method using reflection, the assembly will be loaded into the current application domain from which the method is called. The consequence of this will be the inability to unload the assembly, since the assemblies are not unloaded separately (only the entire application domain). The job will fail.

Further I will give "boring methods" which can be not considered. These methods are implemented, as an example, in the application loader. Their purpose is a purely utilitarian task.
  /// <summary> ///    . /// </summary> /// <param name="domain">,      .</param> private static IEnumerable<IExtension> EnumerateExtensions(AppDomain domain) { IEnumerable<string> fileNames = Directory.EnumerateFiles(domain.BaseDirectory, "*.dll"); if (fileNames != null) { foreach (string assemblyFileName in fileNames) { foreach (string typeName in GetTypes(assemblyFileName, typeof(IExtension), domain)) { System.Runtime.Remoting.ObjectHandle handle; try { handle = domain.CreateInstanceFrom(assemblyFileName, typeName); } catch (MissingMethodException) { continue; } object obj = handle.Unwrap(); IExtension extension = (IExtension)obj; yield return extension; } } } } /// <summary> ///    ,    . ///     . /// </summary> /// <param name="assemblyFileName">   </param> /// <param name="interfaceFilter"> </param> /// <param name="domain">   .</param> /// <returns>   .</returns> private static IEnumerable<string> GetTypes(string assemblyFileName, Type interfaceFilter, AppDomain domain) { Assembly asm = domain.Load(AssemblyName.GetAssemblyName(assemblyFileName)); Type[] types = asm.GetTypes(); foreach (Type type in types) { if (type.GetInterface(interfaceFilter.Name) != null) { yield return type.FullName; } } } 


Further, two also simple methods, but more important in the context of the article. The methods may seem strange due to the fact that they have only one challenge, but these are just the consequences of eliminating the functionality unnecessary for this article.
  /// <summary> ///   . /// </summary> /// <param name="path">    .</param> /// <returns>  .</returns> static AppDomain CreateDomain(string path) { AppDomainSetup setup = new AppDomainSetup(); setup.ApplicationBase = path; return AppDomain.CreateDomain("Temporary domain", null, setup); } /// <summary> ///   . /// </summary> /// <param name="domain">  .</param> static void UnloadDomain(AppDomain domain) { AppDomain.Unload(domain); } 


Well, and at the end, the Main method ... which all this "uses"
  static void Main(string[] args) { //   ,     . AppDomain domain = CreateDomain(Directory.GetCurrentDirectory()); try { //    . IEnumerable<IExtension> extensions = EnumerateExtensions(domain); foreach (IExtension extension in extensions) //   .     . Console.WriteLine(extension.GetExtensionName()); //      .     . UnloadDomain(domain); } finally { domain = null; GC.Collect(2); } Console.ReadKey(); } 

Source: https://habr.com/ru/post/142643/


All Articles