System.Reflection名前空間とSystem.Typeを使用すると、ロード済みアセンブリやその中で定義された型(クラス、インターフェース、値型など)に関する情報を取得できます。さらに、実行時に型インスタンスの生成、メソッド呼び出し、プロパティアクセスを動的に行うことが可能です。
開発プロセスでは通常、以下の手順で型を利用します:
- 対象の型を含むアセンブリへの参照を追加
- コード内で直接型名、メソッド名、プロパティ名を使用
- コンパイル成功後にプログラム実行
しかし、開発時点で参照できないアセンブリ内の型を扱う必要がある場合、従来の方法ではコンパイルエラーが発生します。このような「動的参照」のシナリオで威力を発揮するのがリフレクションです。
従来の型使用方法
Worker worker = new Worker();
worker.TaskName = "処理タスク";
worker.ProgressChanged += OnProgressChanged;
worker.ExecuteTask();リフレクションを使用した動的型操作
Assembly targetAssembly = Assembly.LoadFile(@"D:\WorkerLib.dll");
Type workerType = targetAssembly.GetType("WorkerNamespace.Worker");
object workerInstance = Activator.CreateInstance(workerType);
// プロパティ設定
PropertyInfo taskProperty = workerType.GetProperty("TaskName");
if (taskProperty != null)
{
taskProperty.SetValue(workerInstance, "処理タスク", null);
}
// イベント登録
EventInfo progressEvent = workerType.GetEvent("ProgressChanged");
if (progressEvent != null)
{
Type handlerType = progressEvent.EventHandlerType;
progressEvent.AddEventHandler(workerInstance,
Delegate.CreateDelegate(handlerType, this, "HandleProgress"));
}
// メソッド実行
MethodInfo executeMethod = workerType.GetMethod("ExecuteTask", Type.EmptyTypes);
if (executeMethod != null)
{
executeMethod.Invoke(workerInstance, null);
}重要な注意点
- 開発者は対象アセンブリの型情報(名前空間、型名、メンバー定義)を把握している必要があります
- コンパイル時の型チェックが行われないため、実行時エラーのリスクがあります
- パフォーマンス面でオーバーヘッドが生じる可能性があります
実用的な適用例
プラグインアーキテクチャでは、ホストアプリケーションが開発時点で未知のプラグインアセンブリを動的に読み込み、共通インターフェースを通じて操作します。これによりリフレクションの使用を最小限に抑えつつ、拡張性を実現できます。// プラグインローダーの実装例
public void LoadPlugins(string pluginDirectory)
{
foreach (string file in Directory.GetFiles(pluginDirectory, "*.dll"))
{
Assembly pluginAssembly = Assembly.LoadFile(file);
Type pluginType = pluginAssembly.GetTypes()
.FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t));
if (pluginType != null)
{
IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType);
plugin.Initialize();
}
}
}