リフレクションを使用するときは、最初にいくつかの質問をする必要があります。これは、保守が難しい複雑なソリューションになってしまう可能性があるためです。
- 汎用性またはクラス/インターフェイスの継承を使用して問題を解決する方法はありますか?
- 呼び出しを使用して問題を解決できますか
dynamic
(.NET 4.0以降のみ)?
- パフォーマンスは重要ですか。つまり、反映されたメソッドまたはインスタンス化呼び出しは1回、2回、または100万回呼び出されますか?
- テクノロジーを組み合わせて、スマートでありながら実行可能/理解可能なソリューションを実現できますか?
- コンパイル時の型の安全性を失っても大丈夫ですか?
汎用性/動的
あなたの説明から、私はあなたがコンパイル時にタイプを知らないと思います、あなたはそれらがインターフェースを共有することだけを知っていますICalculation
。これが正しければ、上記の番号(1)と(2)はシナリオでは不可能である可能性があります。
パフォーマンス
これは重要な質問です。リフレクションを使用するオーバーヘッドは、400倍を超えるペナルティを妨げる可能性があります。これにより、適度な量の呼び出しでも速度が低下します。
解決は比較的簡単です。を使用する代わりにActivator.CreateInstance
、ファクトリメソッドを使用し(すでにそれがあります)MethodInfo
、デリゲートの作成を検索し、キャッシュして、それ以降デリゲートを使用します。これにより、最初の呼び出しでペナルティが発生するだけで、後続の呼び出しのパフォーマンスはネイティブに近くなります。
テクノロジーを組み合わせる
ここでは多くのことが可能ですが、この方向性を支援するために、あなたの状況をもっと知る必要があります。多くの場合、dynamic
キャッシュされたリフレクションを使用して、ジェネリックスと組み合わせることになります。情報隠蔽を使用する場合(OOPで通常行われているように)、高速で安定した、それでも十分に拡張可能なソリューションが得られる可能性があります。
コンパイル時の型安全性の喪失
5つの質問のうち、これはおそらく最も重要な質問です。リフレクションの間違いについて明確な情報を提供する独自の例外を作成することは非常に重要です。つまり、入力文字列またはその他のチェックされていない情報に基づくメソッド、コンストラクター、またはプロパティへのすべての呼び出しは、try/catchでラップする必要があります。特定の例外のみをキャッチします(いつものように、つまり、自分Exception
自身をキャッチしないでください)。
TargetException
(メソッドは存在しません)、TargetInvocationException
(メソッドは存在しますが、呼び出されると例外が発生します)、、TargetParameterCountException
(MethodAccessException
適切な特権ではなく、ASP.NETで多く発生します)、InvalidOperationException
(ジェネリック型で発生します)に焦点を当てます。必ずしもすべてをキャッチしようとする必要はありません。予想される入力と予想されるターゲットオブジェクトによって異なります。
まとめると
を削除Activator.CreateInstance
し、MethodInfoを使用してfactory-createメソッドを見つけ、を使用Delegate.CreateDelegate
してデリゲートを作成およびキャッシュします。Dictionary
キーがサンプルコードのクラス文字列と等しい静的な場所に保存するだけです。以下は、タイプの安全性をあまり失うことなく、これを安全に行うための迅速ですがそれほど汚くない方法です。
サンプルコード
public class TestDynamicFactory
{
// static storage
private static Dictionary<string, Func<ICalculate>> InstanceCreateCache = new Dictionary<string, Func<ICalculate>>();
// how to invoke it
static int Main()
{
// invoke it, this is lightning fast and the first-time cache will be arranged
// also, no need to give the full method anymore, just the classname, as we
// use an interface for the rest. Almost full type safety!
ICalculate instanceOfCalculator = this.CreateCachableICalculate("RandomNumber");
int result = instanceOfCalculator.ExecuteCalculation();
}
// searches for the class, initiates it (calls factory method) and returns the instance
// TODO: add a lot of error handling!
ICalculate CreateCachableICalculate(string className)
{
if(!InstanceCreateCache.ContainsKey(className))
{
// get the type (several ways exist, this is an eays one)
Type type = TypeDelegator.GetType("TestDynamicFactory." + className);
// NOTE: this can be tempting, but do NOT use the following, because you cannot
// create a delegate from a ctor and will loose many performance benefits
//ConstructorInfo constructorInfo = type.GetConstructor(Type.EmptyTypes);
// works with public instance/static methods
MethodInfo mi = type.GetMethod("Create");
// the "magic", turn it into a delegate
var createInstanceDelegate = (Func<ICalculate>) Delegate.CreateDelegate(typeof (Func<ICalculate>), mi);
// store for future reference
InstanceCreateCache.Add(className, createInstanceDelegate);
}
return InstanceCreateCache[className].Invoke();
}
}
// example of your ICalculate interface
public interface ICalculate
{
void Initialize();
int ExecuteCalculation();
}
// example of an ICalculate class
public class RandomNumber : ICalculate
{
private static Random _random;
public static RandomNumber Create()
{
var random = new RandomNumber();
random.Initialize();
return random;
}
public void Initialize()
{
_random = new Random(DateTime.Now.Millisecond);
}
public int ExecuteCalculation()
{
return _random.Next();
}
}