プラグイン機能を実装する

過去のブログのアーカイブ
この記事は前身のブログのアーカイブを引き継いだものです. 画像が正しく表示できないなど,コンテンツの表示に問題がある恐れがあります.

C#でプラグイン機能を実装する方法をご紹介。参考サイトはいくらかあるのですが、私なりにアレンジしています。

プログラムを自由に拡張するために必要なプラグインの機能。ついでにアドオンや拡張機能もプラグインと同等です。
1つのdllファイルに幾つかのインターフェースを実装している場合、それも同時に取得する方法ことができるように改良しました。

ソースコード

だいぶ長いです。

using System;
using System.Collections.Generic;
using System.IO;
public class PluginInfo
{
    private PluginInfo(string filename)
    {
        this.FileName = filename;
    }
    public string FileName { get; private set; }
    public static PluginInfo[] FindPlugin<T>(string folder)
    {
        return FindPlugin(typeof(T), folder);
    }
    public static PluginInfo[] FindPlugin(Type hasType, string folder)
    {
        Type t = hasType;
        List<PluginInfo> res = new List<PluginInfo>();
        foreach (string file in Directory.GetFiles(folder, "*", SearchOption.AllDirectories))
        {
            try
            {
                //アセンブリとして読み込む
                System.Reflection.Assembly asm =
                    System.Reflection.Assembly.LoadFrom(file);
                foreach (Type type in asm.GetTypes())
                {
                    //アセンブリ内のすべての型について、
                    //プラグインとして有効か調べる
                    if (type.IsClass && t.IsPublic && !type.IsAbstract &&
                        type.GetInterface(t.FullName) != null && type.IsSubclassOf(t))
                    {
                        //PluginInfoをコレクションに追加する
                        res.Add(new PluginInfo(file));
                        break;
                    }
                }
            }
            catch { }
        }
        return res.ToArray();
    }
    public T[] GetMembers<T>()
    {
        Type t = typeof(T);
        List<T> res = new List<T>();
        //アセンブリを読み込む
        System.Reflection.Assembly asm = System.Reflection.Assembly.LoadFrom(this.FileName);
        foreach (Type type in asm.GetTypes())
        {
            try
            {
                if (type.IsClass && t.IsPublic && !type.IsAbstract &&
                    (type.GetInterface(t.FullName) != null || type.IsSubclassOf(t)))
                {
                    //クラス名からインスタンスを作成する
                    res.Add((T)asm.CreateInstance(type.FullName));
                }
            }
            catch
            {
            }
        }
        return res.ToArray();
    }
}

このソースコードの特徴は、どのようなプラグイン用のクラスであれ検索できること。FindPluginメソッドにプラグインの型をジェネリックとしてつけると検索します。
検索範囲はすべてのファイルを検索しようとするので、dllファイルのみという場合はソースコードを編集してください。
あとこの方法だとインターフェースの他に継承クラスも検索されます。高性能なプラグイン機能となるとインターフェースより継承クラスの方がよく使われる気がします。

使い方

プラグインを検索する

もしもIPluginというインターフェースが実装されたプラグインファイルを検索したい場合

var plugins = PluginInfo.FindPlugin<IPlugin>(Application.StartupPath);

と、この1行で実装できます。戻り値はPluginInfoクラスの配列。

オブジェクトを取得する

上記のコードの続きにこのコードを書いてください。

foreach (var plug in plugins) {
    // IPluginインターフェースが実装されたインターフェースのオブジェクトの取得
    foreach (var obj in olug.GetMembers<IPlugin>()) {
        Console.WriteLine(obj.ToString());
    }
}

これだけで簡潔します。
もしも、IPluginインターフェースは1つしか取得しないぞ!という場合は

plug.GetMembers<IPlugin>().First();
// 又は
plug.GetMembers<IPlugin>()[0];

とすればもっと簡単に取得できます。前者の書き方だと.Net Framework 4.0から、Linqを使う必要がありますが…

 参考

こちらのソースコードを参考にしました。あくまで上記のコードはこのサイトのコードを改良しただけですからね
プラグイン機能を持つアプリケーションを作成する
http://dobon.net/vb/dotnet/programing/plugin.html