プラグインがある .NET Core アプリケーションを作成する という記事が公式にありますが、読んでもよくわかりません。
learn.microsoft.com
というわけで、試してみました。
できました。
using System.Reflection; using System.Runtime.Loader; // 使っているところ { var contexts = ExtendedLibraryLoadContext.LoadExtensions("../../../../test/").ToArray(); var Assemblies = contexts.SelectMany(x => x.Assemblies).OrderBy(x => x.FullName); foreach (var assembly in Assemblies.Select((x, i) => (x, i))) Console.WriteLine($"{assembly.i}: {assembly.x.FullName}"); } // AssemblyLoadContext クラス public class ExtendedLibraryLoadContext : AssemblyLoadContext { public string PluginDirectory { get; init; } public bool IsSearchAllDirectories { get; init; } public ExtendedLibraryLoadContext(string pluginDirectory) : this(pluginDirectory, true) { } public ExtendedLibraryLoadContext(string pluginDirectory, bool isSearchAllDirectories) { PluginDirectory = pluginDirectory; IsSearchAllDirectories = isSearchAllDirectories; } // 読み込むところ。return 値で読んだ Assembly を返すための処理が入っているのでいらなければ消す private IEnumerable<Assembly> Load(string[] files) { var dllFiles = files.Select(file => Path.GetFullPath(file)); var loaded = new List<Assembly>(); foreach (var dllFile in dllFiles) { var assembly = LoadFromAssemblyPath(dllFile); loaded.Add(assembly); } return loaded.ToArray(); } public IEnumerable<Assembly> Load() => Load(Directory.GetFiles(PluginDirectory, "*.dll", IsSearchAllDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly).ToArray()); // 外から呼ばれる読むところ public static IEnumerable<ExtendedLibraryLoadContext> LoadExtensions(string pluginDirectory) { // 2 階層のディレクトリをそれぞれプラグインとして分けて読む var directories = Directory.GetDirectories(pluginDirectory).Where(directory => Directory.GetFiles(directory, "*.dll", SearchOption.AllDirectories).Any()).ToArray(); var contexts = directories.Select(directory => new ExtendedLibraryLoadContext(directory)).ToArray(); foreach (var context in contexts) context.Load(); // 1 階層目はそれだけで一つの Context で読む if (Directory.GetFiles(pluginDirectory, "*.dll", SearchOption.TopDirectoryOnly).Any()) { var context = new ExtendedLibraryLoadContext(pluginDirectory, false); context.Load(); contexts = new[] { context }.Concat(contexts).ToArray(); } return contexts; } }
ソリューションありのプロジェクトで、.sln ファイルと同じ階層に test ディレクトリがあって、それがプラグイン格納フォルダという想定です。
test 直下の dll はそれで一組、それより階層の深いディレクトリは test 直下のディレクトリをプラグインごとのディレクトリをみなして、再帰でディレクトリを掘って一組にしています。
元の記事では ICommand インターフェイスという話も出てきましたが、そこはただのリフレクションなのであるとノイズになるので消し去りました。
■ 概要
プラグインごとに AssemblyLoadContext (の派生クラス) のインスタンス作って、それぞれ LoadFromAssemblyPath で dll を読みます。
これで、プラグインごとにバージョンの違う DLL に依存していても大丈夫、ということだと思います。
コードのほとんどの部分がディレクトリを探したり、ディレクトリの中の dll ファイルを探したりするコードなので、ファイル関係のコードは消し去って読んでみてください。ほとんど残りません。
ちなみに公式のサンプルが AssemblyLoadContext の派生クラスを作っていたのでそうしましたが、AssemblyLoadContext クラス自体もインスタンス化できそうなので、派生クラス作らなくても使えるかもしれません。