.NETでのアセンブリの動的なロード、操作、およびアンロード

多くの場合、開発者はメインアルゴリズムを同様のタスクで拡張するという問題に直面します。 たとえば、ユーザーに単一のインターフェイスを提供するさまざまなサービスのアグリゲーターが、数百のサービスプロバイダーにリクエストを送信します。 このタスクは、メインコアが特定のインターフェイスのさまざまな実装を使用してアセンブリを動的にロードできるようにします。 ここでは、.NETプログラマーの過労は最初に予想されません。 「リフレクション」という用語が知られている場合、おそらく既に通り抜けたいですか? しかし、このトピックでは、リフレクション自体については話していません...最も「きれいに」行う方法を説明します。 つまり 1つのニュアンスで-調査中のアセンブリは、操作後にアンロードする必要があります。





そのため、タスク:コンソールアプリケーションExtensionLoaderを作成する必要があります。これは、起動時に、ディレクトリからIExtensionインターフェイスを実装するクラスが見つかったすべてのライブラリを動的に読み込みます。

interface IExtension { String GetExtensionName(); }
      
      





見つかったライブラリを読み込んだ後、アプリケーションは、見つかったクラスごとに(IExtensionを使用して)インスタンスを作成し、GetExtensionName()メソッドのみを実行します。結果はコンソールに表示されます。



それはテストタスクのように聞こえます-実際、そうでした...



このタスクの1つのニュアンスは非常に興味深いものでした。アプリケーションのアーキテクチャは、ロードされたすべてのライブラリをアンロードできるように構築する必要があります。 ここからが楽しみです。 クイックグーグルでは、既製のソリューションは提供されませんでした。 問題の分析にはある程度の時間がかかりましたが、私はそれを共有したいと思います...建設的な反応を得て、無駄に嘘をつかないことを望んでいます...



インターフェースの説明を含む共​​通ライブラリーから始めましょう。 それはそれ自体が別個のプロジェクトになるため、このインターフェイスのすべての実装に固執することなく、拡張機能とシステムのコアの両方に接続できます。 それは一種の、明らかにです。 ライブラリには、割り当て時に、1つのインターフェイスが含まれます。 タスクですでに説明されているので、繰り返しません。



次に、IExtensionインターフェイスを実装します。 また、これが別のプロジェクトになることも明らかです。この例では、特定のインターフェイスの1つの実装が含まれます。

  public class Extension1 : MarshalByRefObject, IExtension { public Extension1() { } public string GetExtensionName() { return "Extension 1 from " + AppDomain.CurrentDomain.FriendlyName; } }
      
      







好奇心reader盛な読者は、ここで見ることを期待していなかったいくつかの過剰にすでに気付いています。 つまり、MarshalByRefObjectからの継承。 ここでのコツは次のとおりです。MarshalByRefObjectを継承しない場合、リフレクションを使用してメソッドを実行しようとすると、メソッドが呼び出される現在のアプリケーションドメインにアセンブリが読み込まれます。 アセンブリは個別にアンロードされないため(アプリケーションドメイン全体でのみ完全にアンロードされるため)、この結果はアセンブリのアンロードが不可能になります。 タスクは失敗します。



次に、見る必要のない「退屈な方法」を紹介します。 これらのメソッドは、例として、ローダーアプリケーションに実装されています。 彼らの目的は純粋に功利主義的な仕事です。

  /// <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; } } }
      
      







以下も2つの単純なメソッドですが、この記事のコンテキストではより重要です。 メソッドは呼び出しが1つしかないため、奇妙に見えるかもしれませんが、これらはこの記事に不要な機能を削除した結果にすぎません。

  /// <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); }
      
      







さて、そして最後に、これをすべて「使用する」メインメソッド...

  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(); }
      
      






All Articles