以下の内容はhttps://rksoftware.hatenablog.com/entry/2025/03/25/211554より取得しました。


C# でアプリのプラグインを読み込む

プラグインがある .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 クラス自体もインスタンス化できそうなので、派生クラス作らなくても使えるかもしれません。




以上の内容はhttps://rksoftware.hatenablog.com/entry/2025/03/25/211554より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14