読者です 読者をやめる 読者になる 読者になる
ようこそ。睡眠不足なプログラマのチラ裏です。

特定のインターフェイスを持つプラグインを取得

プログラミング C#3.0 VB.NET LINQ

以下のような感じで、特定のインターフェイスを持つプラグインを取得するメソッドを作ってみた。
複数の種類のプラグインを利用するアプリなんてそうそうない気がしないでもないが、
こんな感じでひとつ作っておくと便利。やっぱLINQとyield使うと、かなりすっきり*1


いつものように例はC#で書きますが、VB.NETでも同様のことができます。(yield 以外)

using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;

namespace Utility
{
    public class PluginUtil
    {
        /// <summary>
        /// 指定したインターフェイスのプラグインを取得します。
        /// </summary>
        /// <typeparam name="T">プラグインのインターフェイス</typeparam>
        /// <param name="path">プラグイン(*.dll)のパスを指定</param>
        /// <returns></returns>
        public static IEnumerable<T> GetPlugins<T>(string path)
        {
            if (!typeof(T).IsInterface) yield break;
            var plugins = from dll in Directory.GetFiles(path, "*.dll")
                          let asm = Assembly.LoadFrom(dll)
                          from t in asm.GetTypes()
                          from i in t.GetInterfaces()
                          where t.GetConstructor(new Type[]{}) != null
                          where t.IsClass
                          where t.IsPublic
                          where i == typeof(T)
                          where !t.IsAbstract
                          orderby t.Name
                          select (T)Activator.CreateInstance(t);

            foreach (var plugin in plugins)
            {
                yield return plugin;
            }
        }
    }
}


プラグインのインターフェイスを定義

お試しで適当プラグインを作成。

namespace Plugin
{
    /// <summary>
    /// プラグインで実装するインターフェイス
    /// </summary>
    public interface IPlugin
    {
        /// <summary>
        /// プラグインの説明を取得します。
        /// </summary>
        string Description { get; }

        /// <summary>
        /// プラグインを実行します。
        /// </summary>
        void Run();

        /// <summary>
        /// プラグインのインスタンス作成直後の初期化
        /// </summary>
        /// <param name="host">プラグインのホスト</param>
        void Initialize(IPluginHost host);
    }
}

続きまして、適当プラグインに対する、ホスト側のインターフェイスを定義します。

namespace Plugin
{
    /// <summary>
    /// プラグインのホストで実装するインターフェイス
    /// </summary>
    public interface IPluginHost
    {
        /// <summary>
        /// テキスト
        /// </summary>
        string Text { get; }

        /// <summary>
        /// ホストでメッセージを表示する
        /// </summary>
        /// <param name="plugin">メソッドを呼び出すプラグイン</param>
        /// <param name="msg">表示するメッセージ</param>
        void ShowMessage(IPlugin plugin, string msg);
    }
}

プラグインのインターフェイスを実装

実装その1

using System.Text;
using Plugin;

namespace CountBytes
{
    /// <summary>
    /// 文字のバイト数を表示するためのプラグイン
    /// </summary>
    public class CountBytes : IPlugin
    {
        private Plugin.IPluginHost _host;

        #region IPlugin メンバ

        public string Description
        {
            get
            {
                return "入力された文字のバイト数を取得します。";
            }
        }

        public void Initialize(Plugin.IPluginHost host)
        {
            this._host = host;
        }

        public void Run()
        {
            Encoding sjisEnc = Encoding.GetEncoding("Shift_JIS");
            var num = sjisEnc.GetByteCount(_host.Text);
            _host.ShowMessage(this, num.ToString());
        }

        #endregion
    }
}

実装その2

using Plugin;

namespace CountChars
{
    /// <summary>
    /// 文字数を表示するためのプラグイン
    /// </summary>
    public class CountChars : IPlugin
    {
        private Plugin.IPluginHost _host;

        #region IPlugin メンバ

        public string Description
        {
            get
            {
                return "入力された文字数を取得します。";
            }
        }

        public void Initialize(Plugin.IPluginHost host)
        {
            this._host = host;
        }

        public void Run()
        {
            _host.ShowMessage(this,_host.Text.Length.ToString());
        }

        #endregion
    }
}

サンプルなので単純に(^ω^;)


プラグインのホストの実装と実行

適当プラグインのホストの実装、およびプラグインの実行を行います。
特定インターフェイスのプラグインを取得することができる、
public static IEnumerable GetPlugins(string path)メソッドを呼び出しているところに注目。

using System;
using Plugin;
using Utility;

namespace ConsoleApplication1
{
    public class Program 
    {
        private static void Main()
        {
            Console.WriteLine("何か文字列を入力してください。");

            //入力の受付
            var input = Console.ReadLine();
            var t = new Test(input);

            foreach (var plugin in PluginUtil.GetPlugins<IPlugin>("."))
            {
                Console.WriteLine("プラグイン名:{0}", plugin.Description);
                plugin.Initialize(t);
                plugin.Run();
            }

            Console.ReadLine();
        }
    }

    public class Test : IPluginHost
    {
        public Test(string text) {
            _text = text;
        }
        #region IPluginHost メンバ

        private static string _text;
        string IPluginHost.Text
        {
            get { return _text; }
        }

        void IPluginHost.ShowMessage(IPlugin plugin, string msg)
        {
            Console.WriteLine(msg);
        }

        #endregion
    }
}


実行結果

何か文字列を入力してください。
abcあいう
プラグイン名:入力された文字のバイト数を取得します。
9
プラグイン名:入力された文字数を取得します。
6

正しくプラグインを取得し、実行することができました。おしまい。


関連記事:動的にDLL(ライブラリ)をロードしてアセンブリを作って、P/Invokeなdelegateを生成してみよう。

*1:LINQって、最初は全然直感的じゃないじゃんって思ってたけど、いつの間にか直感的に記述できるなーと、印象が変わってきた