4

シンプルなプラグイン アーキテクチャを使用して入出力を制御するプログラムを C# (現時点では 3.5 ですが、必要に応じて他のバージョンにも適用できる可能性があります) で作成しています。各プラグインは、ユーザーが使用するプラグインを選択したときにロードされる DLL です。

実際のプラグイン クラスは実行時までわからないため、ラッパー クラスでリフレクションを使用してメソッドを呼び出し、プラグインのプロパティにアクセスしています。

これまで、プラグインのメソッドを呼び出すために以下を使用してきました。

public object Method(string methodName, params object[] arguments) {
  // Assumed variables/methods/exceptions:
  //   Dictionary<string, MethodInfo> Methods: a cache of MethodInfo's
  //     of previously called methods.
  //   NoSuchMethodException: thrown if an unknown/unreachable method is
  //     requested. The message member contains the invalid method name
  //   void LoadMethod(string methodName, params object[] arguments): responsible
  //     for retrieving the MethodInfo's, or throw a NoSuchMethodException
  //   object Plugin: an instance of the dynamically loaded class.
  if (!Methods.ContainsKey(methodName)) {
    LoadMethod(methodName, arguments);
  }
  if (arguments != null && arguments.Length == 0) {
    arguments = null;
  }
  return Methods[methodName].Invoke(Plugin, arguments);
}

次のように使用されます。

string[] headers = (string[]) Plugin.Method("GetHeaders", dbName, tableName);

これは、呼び出し元が戻り値を期待される型に正しくキャストしている限り、うまく機能します。プラグインは特定のインターフェースを実装する必要があるため、呼び出し元はこのタイプを知っている必要があります。

ただし、リフレクションでさらに作業を行った後、次の代替形式が思い浮かびました。

public T Method<T>(string methodName, params object[] arguments) {
  if (!Methods.ContainsKey(methodName)) {
    LoadMethod(methodName, arguments);
  }
  if (Methods[methodName].ReturnType != typeof(T)) {
    // Could also move this into LoadMethod to keep all the throwing in one place
    throw new NoSuchMethodException(methodName);
  }
  if (arguments != null && arguments.Length == 0) {
    arguments = null;
  }
  return (T) Methods[methodName].Invoke(Plugin, arguments);
}

これは次のように使用されます。

string[] headers = Plugin.Method<string[]>("GetHeaders", dbName, tableName);

このバージョンでは、基本的にキャストがメソッド メソッドに移動されます。呼び出し元は、期待される戻り値の型を知る必要があることは明らかですが、それは常に当てはまります。void メソッドでは機能しませんが、メソッドのバージョンを含めることができます。

public void Method(string methodName, params object[] arguments) {
  // Good for void methods, or when you're going to throw away the return
  // value anyway.
  if (!Methods.ContainsKey(methodName)) {
    LoadMethod(methodName, arguments);
  }
  if (arguments != null && arguments.Length == 0) {
    arguments = null;
  }
  Methods[methodName].Invoke(Plugin, arguments);
}

私の質問は、これらの 1 つが他のものよりも本質的に優れているかどうかです (「より良い」値が与えられた場合)。たとえば、著しく高速ですか?分かりやすい?さらにサポートされますか?

個人的には後者の見た目が好きですが、戻り値の型テスト ( Methods[methodName].ReturnType != typeof(T)) が単純すぎるのではないかと少し心配です。興味深いことに、元々は でしたが、それは!(Methods[methodName].ReturnType is T)常に失敗しているように見えました。

私が見つけたこれに最も近い既存の質問はGeneric method to type castsであり、回答のいくつかは、後者の方法が前者よりも高価であることを示唆していましたが、そこには詳細な方法はあまりありません(質問はよりどちらが優れているかではなく、メソッドの実装)。

明確化:これは、IPlugin を使用しない、非常に限られた手動のプラグイン システムです。この状況で呼び出し元がキャストすることを期待するよりも、ジェネリックメソッドが本質的に優れているか悪いかという問題にもっと興味があります。

4

1 に答える 1

3

あなたの質問に関しては、両方を提供する必要があると思います。ジェネリック バージョンで非ジェネリック バージョンを呼び出すだけです。両方の長所を活用できます。パフォーマンスに関しては、メソッドを動的に呼び出してオブジェクト配列を構築する場合と比較して、オブジェクトのキャストにかかる時間がどれほど短いかを検討してください。ここでパフォーマンスを考慮する価値はありません。私はスタイルの観点から一般的なアプローチを好みますが、必要に応じて型の制約を適用できると考えています。

public T Method<T>(string methodName, params object[] arguments)
{
    return (T)Method(methodName, arguments);
}

サイドバー

あなたの設計を理解していれば、あなたの実装は期待されるインターフェースにキャストする必要があると思います。プラグインがサポートすべきメソッドがわかっている場合は、リフレクションを使用するべきではありません。

var plugin = (IPlugin)Activator.CreateInstance(pluginType);
var headers = plugin.GetHeaders(dbName, tableName);

少し違うことを試して、カスタムのランタイム動作を登録できるインターフェイスをプラグインに実装するように要求することができます。

public interface IPlugin
{
     void Load(IAppContext context);
     void Unload();
}

読み込み方法は次のようになります。

void LoadPlugins(Assembly a)
{
    var plugins = 
        a.GetTypes()
        .Where(t => typeof(IPlugin).IsAssignableFrom(t))
        .Select(t => (IPlugin)Activator.CreateInstance(t))
        .ToList();
     Plugins.AddRange(plugins);
     foreach (var p in plugins)
     {
         p.Load(Context);
     }
}
于 2013-04-24T03:43:40.840 に答える