1. はじめに
リフレクション(英語: Reflection)とは、実行時に自分のプログラムの構造を調べられる機能です。プログラムが自分自身に「どんなメソッドがある? この型にはどんなプロパティがある?」と聞いて、ランタイムで答えを得られると想像してください。
実際の利用例
- プラグインやモジュールの作成: どのクラスを扱うか事前に知らなくても、DLL をロードしてそのメソッドをランタイムで調べれば十分です。
- テストの自動化: xUnit や NUnit のようなテストフレームワークは、リフレクションでテストメソッドを見つけます。
- シリアライゼーション: リフレクションを使えば、プロパティを手動で列挙しなくても任意のオブジェクトを自動的に JSON/XML に変換できます。
- 属性に基づくバリデーション: たとえばプロパティに [Required] 属性を付けておけば、そういうプロパティを自動でチェックできます。
面接で知っておく理由
リフレクションの扱いは面接で聞かれることが多いです。なぜなら .NET の内部構造への理解や、動的な問題を解く能力を示せるからです。自分のフレームワークや拡張機能を書きたいなら、リフレクションは必須の技術です。
2. リフレクションの名前空間と基本クラス
C# のリフレクション機能は全て System.Reflection 名前空間から利用できます。ファイルの先頭で忘れずに:
using System.Reflection;
主なリフレクションの型
| クラス/型 | 説明 |
|---|---|
|
.NET の型の説明を表します(例えばクラス、インターフェイス、enum など) |
|
型のプロパティ |
|
型のメソッド |
|
型のフィールド |
|
型のコンストラクタ |
|
アセンブリ(EXE や DLL)を表します |
Type の主なメソッド
| メソッド | 説明 |
|---|---|
|
すべての public なプロパティを返します |
|
すべての public なフィールドを返します |
|
すべての public なメソッドを返します |
|
すべての public なコンストラクタを返します |
|
その型が実装するすべてのインターフェイスを返します |
|
型に適用されたすべての属性を返します |
これらのメソッドには BindingFlags を渡して private や static メンバーにアクセスすることができますが、ここでは基本的なシナリオに限定します。
3. 最初の一歩: Type オブジェクトを取得する
リフレクションの全てのテクニックは Type オブジェクトを扱うことに基づいています。
Type を取得する方法はいくつかあります。順に見ていきましょう:
方法 1: オブジェクトから
string text = "Hello, Reflection!";
Type type1 = text.GetType();
Console.WriteLine(type1.Name); // String
方法 2: 型名から
Type type2 = typeof(int);
Console.WriteLine(type2.FullName); // System.Int32
方法 3: 文字列から(動的に)
Type type3 = Type.GetType("System.Double");
Console.WriteLine(type3); // System.Double
注意してください! 他のアセンブリのユーザ定義クラスの場合はアセンブリ名を含む完全修飾名を指定する必要があります。
4. 型の構造を調べる: プロパティ、メソッド、フィールド、コンストラクタ
いよいよ本番です: Type オブジェクトを取得したら、欲しい情報は全部取れます。
プロパティ一覧の取得
Type type = typeof(DateTime);
PropertyInfo[] properties = type.GetProperties();
foreach (var prop in properties)
{
Console.WriteLine($"{prop.PropertyType.Name} {prop.Name}");
}
出力例:
Int32 Day
Int32 Month
Int32 Year
DayOfWeek DayOfWeek
...
メソッド一覧の取得
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
{
Console.WriteLine($"{method.ReturnType.Name} {method.Name}()");
}
大きな型だとメソッドが大量にあります! 多くの場合、全てではなく自分の定義したメンバー(例えば object から継承したものを除く)だけが必要です。後でその処理を説明します。
フィールド一覧の取得
FieldInfo[] fields = type.GetFields();
foreach (var field in fields)
{
Console.WriteLine($"{field.FieldType.Name} {field.Name}");
}
通常のクラスは public フィールドを持たないことが多いです。カプセル化は大事です :)
コンストラクタの取得
ConstructorInfo[] constructors = type.GetConstructors();
foreach (var ctor in constructors)
{
Console.WriteLine($"コンストラクタ: {ctor}");
}
5. 実践的な例: アプリケーションの解析
学習プロジェクトには TaskItem クラスがあり、そこにタスクを保持しています。これをリフレクションで調べてみましょう。
public class TaskItem
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DueDate { get; set; }
public bool IsCompleted;
}
ランタイムで型の「中身」を知るにはこんな感じです:
Type taskType = typeof(TaskItem);
Console.WriteLine("プロパティ:");
foreach (var prop in taskType.GetProperties())
Console.WriteLine($" {prop.PropertyType.Name} {prop.Name}");
Console.WriteLine("フィールド:");
foreach (var field in taskType.GetFields())
Console.WriteLine($" {field.FieldType.Name} {field.Name}");
Console.WriteLine("メソッド:");
foreach (var method in taskType.GetMethods())
Console.WriteLine($" {method.ReturnType.Name} {method.Name}()");
クラスに新しいプロパティを追加して、リフレクションがそれをどう見つけるか試してみてください。汎用シリアライザやオブジェクトインスペクタはこうやって動きます。
6. 可視化: Type オブジェクトの構造(図)
+-------------------------+
| Type |
+-------------------------+
| .Name |
| .FullName |
| .Namespace |
| .Assembly |
+-------------------------+
| .GetProperties() |
| .GetFields() |
| .GetMethods() |
| .GetConstructors() |
| .GetInterfaces() |
| .GetCustomAttributes() |
+-------------------------+
これが型の「ポートレート」です — 名前やアセンブリ、メンバーや属性のセットを持っています。
型の基本情報を取得する
Type オブジェクトから次のような情報が取れます:
Type t = typeof(double);
Console.WriteLine(t.Name); // Double
Console.WriteLine(t.FullName); // System.Double
Console.WriteLine(t.Namespace); // System
Console.WriteLine(t.IsClass); // False
Console.WriteLine(t.IsValueType); // True
Console.WriteLine(t.IsEnum); // False
Console.WriteLine(t.IsPrimitive); // True
7. 名前で特定のプロパティ・メソッド・フィールドを検索する
プロパティ
var prop = taskType.GetProperty("Title");
if (prop != null)
{
Console.WriteLine($"プロパティ 'Title' の型: {prop.PropertyType}");
}
メソッド
var method = taskType.GetMethod("ToString");
if (method != null)
{
Console.WriteLine($"メソッド 'ToString' の戻り値: {method.ReturnType}");
}
フィールド
var field = taskType.GetField("IsCompleted");
if (field != null)
{
Console.WriteLine($"フィールド 'IsCompleted' の型: {field.FieldType}");
}
8. 値への動的アクセスにリフレクションを使う
見るだけでなく、値に「触る」こともできます!
プロパティの読み取り
TaskItem item = new TaskItem { Id = 1, Title = "リフレクションをテストする", DueDate = DateTime.Today };
PropertyInfo titleProp = item.GetType().GetProperty("Title");
if (titleProp != null)
{
object value = titleProp.GetValue(item);
Console.WriteLine($"タイトル: {value}");
}
プロパティの設定
titleProp.SetValue(item, "変更されたタイトル");
Console.WriteLine(item.Title);
フィールドの操作
FieldInfo completedField = item.GetType().GetField("IsCompleted");
completedField.SetValue(item, true);
Console.WriteLine(item.IsCompleted); // true
private なフィールドやプロパティにアクセスするには BindingFlags.NonPublic のようなフラグが必要です。これは後で詳しく説明します。
9. リフレクションでメソッドを呼び出す
書いたときに知らなかったメソッドでも呼び出せます!
TaskItem task = new TaskItem { Title = "リフレクションは最高!" };
MethodInfo method = task.GetType().GetMethod("ToString");
if (method != null)
{
object result = method.Invoke(task, null);
Console.WriteLine(result);
}
パラメータがあるメソッドの場合は、オブジェクトの配列で渡します:
public class Calculator
{
public int Add(int a, int b) => a + b;
}
var calc = new Calculator();
var addMethod = typeof(Calculator).GetMethod("Add");
object[] parameters = { 5, 3 };
object sumResult = addMethod.Invoke(calc, parameters);
Console.WriteLine(sumResult); // 8
10. コンストラクタを使った動的オブジェクト生成
リフレクションを使えば、new 演算子を使わなくてもオブジェクトを生成できます!
Type taskType = typeof(TaskItem);
object newTask = Activator.CreateInstance(taskType);
// これは同じ TaskItem(ただし型は object)
Console.WriteLine(newTask.GetType().Name); // TaskItem
コンストラクタに引数を渡すこともできます:
public class User
{
public string Name { get; }
public User(string name) { Name = name; }
}
Type userType = typeof(User);
object user = Activator.CreateInstance(userType, "エリス");
Console.WriteLine(((User)user).Name); // エリス
11. System.Reflection を使うときのよくあるミス
ミス №1: BindingFlags なしで private メンバーにアクセスしようとすること。
デフォルトではリフレクションは public メンバーしか見えません。private フィールドやメソッドにアクセスするには BindingFlags.NonPublic が必要です。
ミス №2: null チェックを無視すること。
GetProperty、GetMethod などはメンバーが見つからないと null を返します。チェックしないと NullReferenceException になります。
ミス №3: 間違ったオーバーロードを選んでしまうこと。
同名で複数のオーバーロードがある場合、GetMethod が期待したものを返さない可能性があります。パラメータの型を指定するオーバーロードを使ってください。
ミス №4: パフォーマンスが重要なコードでリフレクションを多用すること。
リフレクションは直接呼び出すより遅いです。パフォーマンスが必要な場合はメタデータのキャッシュやデリゲートを使うなど工夫してください。
GO TO FULL VERSION