カテゴリー
.NET

C#のジェネリックってすげー便利

img43f09a6e7dd57[1].png

NEMCHI2は通知プラグインの読み込みもできた。

やってて気づいたけど、プロトコルプラグインと通知プラグインでは実装しているインターフェースが違うだけで、特定のインターフェースを持つDLLを検索するという動きは、まるで一緒。
どうにか共通化できないかと考えてみて、ジェネリッククラスが使えそうな気がしたので、調べて試してみたら、おおーこれはかなり便利。
[2007.4.7追記]
なんかうまく行く場合となぜか、T plugin = (T)obj;のところで物言わずメソッドを抜けてしまう場合があるみたいです。エラーも出ずに。やり方がまずいのだろうか。調査中。

とりあえず、こんな感じでプラグイン検索を実装してみた。

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

namespace NemchiCore
{
    public class PluginSearch<T> : IEnumerable<T>
    {

        private List<T> plugins = new List<T>();

        public void Search(string searchPath)
        {
            //DLLの取得
            string[] dlls = Directory.GetFiles(searchPath, "*.dll");

            //アセンブリ取得
            foreach (string dll in dlls)
            {
                Assembly asm = Assembly.LoadFrom(dll);

                //クラス一覧を取得
                Type[] types = asm.GetTypes();
                foreach (Type dlltyp in types)
                {
                   
                    if (IncludedInterface(dlltyp, typeof(T)))
                    {
                        if (dlltyp.IsAbstract == false)
                        {
                            //インスタンス作成
                            Object obj = Activator.CreateInstance(dlltyp);
                            T plugin = (T)obj;                             plugins.Add(plugin);
                        }
                    }
                }
            }
        }

        private static bool IncludedInterface(Type dlltyp, Type includeType)
        {
            Type[] interfaces = dlltyp.GetInterfaces();
            foreach (Type interfaceTyp in interfaces)
            {
                //指定されたインターフェースを実装しているクラスを探す
                if (interfaceTyp.Name == includeType.Name)
                {
                    return true;
                }
            }

            return false;
        }

        #region IEnumerable<T> メンバ

        public IEnumerator<T> GetEnumerator()
        {
            return plugins.GetEnumerator();
        }

        #endregion

        #region IEnumerable メンバ

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return plugins.GetEnumerator();
        }

        #endregion

    }
}

IProtocolインターフェースを持つクラスのインスタンスを全部取得する場合は、

        public static List<IProtocol> GetProtocolList(string searchPath)
        {
            List<IProtocol> protocols = new List<IProtocol>();

            PluginSearch<IProtocol> searcher = new PluginSearch<IProtocol>();
            searcher.Search(searchPath);

            foreach (IProtocol protocol in searcher)
            {
                protocols.Add(protocol);               
            }

            return protocols;

        }

こんな感じ。
キャストが一カ所しかないのが素敵。
Searchの戻り値でListをそのまま返してもいいけど、将来的にListじゃなくなることも考えてForEachで回して取得するようにしてみた。
なんかちょっと変則的かな?というかPluginSearchというクラス名を変えればしっくりくるのか?
まぁ、ちょっといろいろ試してみよう。

でも、こんな感じで型に依存せずにロジックがかけるというのは便利ですね。
うまいこと使えばかなりコードがすっきりしそう。
ただ変な使い方したら一気にわかりづらくなりそうな気もしますが、そこは試行錯誤で(笑)


この記事を書いた人: A-tak

A-tak.com(えいたっく どっとこむ)の管理人。
Apple野郎なおっさんでしたが、ちょっと最近のAppleには飽き気味。
A-tak.comは2002年2月から運営(前身のサイトは1999年3月から)。今年で18年目!

Twitter
Mastodon
Facebook