1. はじめに
C# ではどのクラス(あるいは構造体)もメンバーの集合なんだ。代表的なものは次の通り:
- フィールド (fields): データを保持する変数。
- プロパティ (properties): get/set を持つ「賢い」ラッパー。
- メソッド (methods): 機能を実装する。
- コンストラクター (constructors): インスタンスを作る方法。
- イベント (events): 賢いパブリッシュ/サブスクライブ。
型の中身を覗いて、値を変えたりできる魔法の虫眼鏡を持っていると想像してみて。これがリフレクションだよ。
名前空間とリフレクションの主要型
始める前に、名前空間を忘れずにインポートしてね:
using System.Reflection;
主要なクラスとインターフェース:
| クラス/インターフェース | 説明 |
|---|---|
|
メインの入り口 — ここから全部始まる |
|
フィールドに関する情報 |
|
プロパティに関する情報 |
|
メソッドに関する情報 |
|
コンストラクターに関する情報 |
|
イベントに関する情報 |
|
上のもの全部の共通の「親」 |
2. リフレクションで型のメンバーを取得する
フィールドとプロパティを取得する
例: フィールドとプロパティの列挙
例えば、こんなクラスがあるとする:
public class Person
{
public string Name { get; set; }
public int Age;
private string Secret = "秘密の言葉";
}
フィールドを取得:
Type personType = typeof(Person);
FieldInfo[] fields = personType.GetFields(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var field in fields)
{
Console.WriteLine($"フィールド: {field.Name}, 型: {field.FieldType.Name}, アクセス: {field.Attributes}");
}
プロパティを取得:
PropertyInfo[] properties = personType.GetProperties(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var property in properties)
{
Console.WriteLine($"プロパティ: {property.Name}, 型: {property.PropertyType.Name}");
}
補足
- BindingFlags はどのメンバーを探すかを指定する「フラグ」の集合(public/private、instance/static 等)。
- フィールドとプロパティは違うよ: フィールドは生のデータ、プロパティは裏で get/set を呼ぶラッパー。
メソッドを取得する
MethodInfo[] methods = personType.GetMethods(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var method in methods)
{
Console.WriteLine($"メソッド: {method.Name}");
}
出力されるメソッドは多くて驚くかも。プロパティのゲッター/セッターや、object 由来のメソッドも混ざるよ。
便利なフィルター: 自分で定義した「明示的な」メソッドだけを取りたいなら、名前や属性でさらにフィルターしよう。
どの BindingFlags を使うか
| 何を探すか | 呼び出し例 | 説明 |
|---|---|---|
| 公開フィールド | |
外部から見えるもの全部 |
| 非公開フィールド | |
内部に隠れているもの |
| インスタンスのみ | |
static じゃないものだけ |
| 静的のみ | |
static のものだけ |
| 全部まとめて | |
フルリスト |
3. 型のインスタンスを動的に作成する
なんで便利なの?
コンパイル時に型が分からなくてもインスタンスを作れるんだ。
簡単な方法 — Activator.CreateInstance
// Type を何らかの方法で取得、例えば:
Type myType = typeof(Person);
// オブジェクトを作成:
object obj = Activator.CreateInstance(myType);
// 今 obj は Person のインスタンスだけど object 型だよ
引数なしコンストラクターがないクラスでもパラメーターを渡せるよ:
public class Animal
{
public string Name { get; }
public Animal(string name) { Name = name; }
}
// ...
Type animalType = typeof(Animal);
object dog = Activator.CreateInstance(animalType, "シャーリク");
注意!
Console.WriteLine(dog.GetType().Name); // Animal
もっと細かく — ConstructorInfo を使う
ConstructorInfo ctor = animalType.GetConstructor(new Type[] { typeof(string) });
object dog = ctor.Invoke(new object[] { "シャーリク" });
実務では大抵 Activator で十分だけど、プライベートコンストラクターや正確なシグネチャで作りたいときは ConstructorInfo が役に立つよ。
4. 名前でフィールドやプロパティにアクセスする
フィールドを取得して変更する
Person p = new Person();
Type t = p.GetType();
FieldInfo field = t.GetField("Age");
field.SetValue(p, 42);
Console.WriteLine(field.GetValue(p)); // 42
非公開フィールドを扱う:
FieldInfo secret = t.GetField("Secret", BindingFlags.NonPublic | BindingFlags.Instance);
if (secret != null)
{
secret.SetValue(p, "おっと、秘密がバレた");
Console.WriteLine(secret.GetValue(p));
}
else
{
Console.WriteLine("フィールド Secret が見つかりません");
}
注意! 非公開フィールドへのアクセスを乱用しないで — これはカプセル化の破壊で、バグの原因になり得るよ。
プロパティを取得して変更する
PropertyInfo pi = t.GetProperty("Name");
pi.SetValue(p, "ヴァーシャ");
Console.WriteLine(pi.GetValue(p)); // ヴァーシャ
プロパティはフィールドじゃなくて、裏で動く get/set メソッドだよ。
なぜうまくいかないことがあるの?
フィールドやプロパティが非公開の場合は、必ず適切な BindingFlags(例: NonPublic)を付けて取得してね。プロパティに setter がないなら、リフレクションでも値を書き込めない — そのルールは守られるよ。
5. 名前でメソッドを呼び出す
引数なしの簡単なメソッド呼び出し
public class Greeter
{
public void SayHello()
{
Console.WriteLine("こんにちは!");
}
}
Greeter g = new Greeter();
Type t = g.GetType();
MethodInfo mi = t.GetMethod("SayHello");
mi.Invoke(g, null); // こんにちは!
引数ありのメソッド呼び出し
public class MathOp
{
public int Add(int x, int y) => x + y;
}
MathOp calc = new MathOp();
Type t = calc.GetType();
MethodInfo mi = t.GetMethod("Add");
object result = mi.Invoke(calc, new object[] { 7, 5 });
Console.WriteLine(result); // 12
static メソッドを呼ぶときは、Invoke の最初の引数に null を渡してね。
オーバーロードされたメソッドの扱い
MethodInfo mi = t.GetMethod(
"Add",
new Type[] { typeof(int), typeof(int) }
);
あるいは列挙して自分でフィルタする方法もあるよ:
foreach(var method in t.GetMethods()) {
if (method.Name == "Add" && method.GetParameters().Length == 2)
{
// ... これが対象のメソッド!
}
}
6. 便利なコツ
型を解析するときによく使うメンバー/メソッド/プロパティ
| クラスメンバー | 情報取得用メソッド | 戻りクラス | 読み書き方法 |
|---|---|---|---|
| フィールド (field) | |
|
|
| プロパティ (prop) | |
|
|
| メソッド (method) | |
|
|
| コンストラクター | |
|
|
実務での使いどころ
- ORM フレームワーク(例: Entity Framework はモデルから SQL を組み立てる)で使われる。
- シリアライザやパーサ(例: JSON シリアライザー)で使われる。
- 汎用的なロギング、テスト、UI ジェネレータ系のライブラリ。
- 面接で「汎用的なオブジェクトコピーをどう書く?」なんて話題になることがある。
- プラグインを書くとき、外部コードを読み込んで呼び出す用途。
今は魔法みたいに見えるかもしれないけど、使いこなせるとかなり便利になるよ!
7. よくあるミスと注意点
多くの人が BindingFlags を間違えて、非公開フィールドが見えなかったり、逆に余計なメソッドを全部拾ってきたりする。必ずフラグの組み合わせを確認して: 非公開フィールドなら NonPublic、静的なら Static を付けるんだ。
Invoke でメソッド呼び出すときは、宣言通りの順序と型で引数を渡してね。順序や型が違うと TargetParameterCountException や ArgumentException が出るよ。戻り値は object として返ってくるから、キャスト(あるいはパターンマッチ)を忘れずに。
パフォーマンスにも注意: リフレクションは直接呼び出すより遅い。ホットパスでは見つけた Type/MethodInfo/PropertyInfo をキャッシュしたり、事前にデリゲートを生成したりするといいよ。
プロパティのアクセス用メソッド(例: get_PropertyName や set_PropertyName)をよく見るけど、これはプロパティのアクセサーだから手動で作る必要はないよ。
非公開フィールドやプロパティを変更するとカプセル化を壊すことになる。間違った使い方は取りにくいバグを生むから注意して。ベンおじさんの言葉を借りれば: 「大きな力には大きな責任が伴う」 — 慎重に使おう。
GO TO FULL VERSION