C#リフレクションとは?使い方・メリット・注意点を初心者向けに実例で解説

はじめに

C#リフレクションは、実行時にクラスやメソッド、プロパティなどの情報を調べたり、値を取得・変更したり、メソッドを動的に実行したりできる仕組みです。

通常のC#コードでは、呼び出すクラスやメソッドはコンパイル時に決まっています。たとえば user.Name のように書けば、User クラスに Name プロパティが存在することを前提にコンパイルされます。

一方でリフレクションを使うと、実行時に「このオブジェクトにはどんなプロパティがあるか」「この名前のメソッドは存在するか」「このプロパティの値は何か」といった情報を動的に扱えます。

C#リフレクションは、初心者には少し難しく感じられるかもしれません。しかし、ログ出力、CSV変換、JSON変換、入力チェック、テスト、DIコンテナ、ORMなど、実務でも多くの場面で使われています。

この記事では、C#リフレクションとは何か、基本的な使い方、実例、メリット、注意点、パフォーマンス対策まで初心者向けに解説します。

1. C#リフレクションとは?初心者にもわかる基本概念

1-1. リフレクションの意味:実行時に型情報を調べて操作する仕組み

リフレクションとは、プログラムの実行中に型の情報を調べたり、操作したりする機能です。

C#では、クラス、プロパティ、メソッド、フィールド、コンストラクタ、属性などの情報を実行時に取得できます。

たとえば、次のようなことができます。

C#
var user = new User();
Type type = user.GetType();

Console.WriteLine(type.Name);

このコードでは、user オブジェクトの型情報を GetType() で取得し、クラス名を表示しています。

通常であれば、User クラスであることはコードを書いている時点でわかっています。しかし、リフレクションを使うと、実行時に型を調べることができます。

1-2. 通常のコード実行とリフレクションの違い

通常のコードでは、次のようにプロパティやメソッドを直接呼び出します。

C#
var user = new User();
user.Name = "Taro";
Console.WriteLine(user.Name);

これはコンパイル時に Name プロパティの存在が確認されます。もし Name が存在しなければコンパイルエラーになります。

一方、リフレクションでは次のように文字列でプロパティ名を指定できます。

C#
var user = new User();
Type type = user.GetType();

var property = type.GetProperty("Name");
property?.SetValue(user, "Taro");

Console.WriteLine(property?.GetValue(user));

この場合、"Name" という文字列を使ってプロパティを探しています。そのため、実行時までプロパティが存在するかどうかはわかりません。

通常のコードは安全で高速です。一方、リフレクションは柔軟ですが、実行時エラーやパフォーマンス低下に注意が必要です。

1-3. C#でリフレクションが使われる代表的な場面

C#リフレクションは、次のような場面で使われます。

C#
// オブジェクトの全プロパティをログ出力する
// CSVやJSONの変換処理を自動化する
// カスタム属性を読み取って処理を分岐する
// テストコードでprivateメンバーを確認する
// DIコンテナでクラスを自動登録する
// ORMでエンティティのプロパティを読み取る
// プラグイン機構で動的に型を読み込む

特に、同じような処理を複数のクラスに対して共通化したい場合に便利です。

たとえば、User クラス、Product クラス、Order クラスなど、さまざまなオブジェクトのプロパティ一覧をログ出力したい場合、クラスごとに処理を書くと重複が増えます。

リフレクションを使えば、どのクラスでも共通の処理として扱えます。

1-4. System.Reflection名前空間とTypeクラスの役割

C#でリフレクションを使うときは、主に System.Reflection 名前空間を利用します。

C#
using System.Reflection;

また、型情報を扱う中心的なクラスが Type クラスです。

C#
Type type = typeof(User);

Type クラスを使うと、次のような情報を取得できます。

C#
type.Name              // クラス名
type.FullName // 名前空間を含む完全名
type.GetProperties() // プロパティ一覧
type.GetMethods() // メソッド一覧
type.GetFields() // フィールド一覧
type.GetConstructors() // コンストラクタ一覧

リフレクションでは、まず Type を取得し、そこから PropertyInfoMethodInfoFieldInfo などを取得して操作する流れが基本です。

2. C#リフレクションでできること

2-1. クラス名・メソッド名・プロパティ名を取得する

リフレクションを使うと、クラス名やメソッド名、プロパティ名を実行時に取得できます。

C#
public class User
{
public string Name { get; set; } = "";
public int Age { get; set; }

public void SayHello()
{
Console.WriteLine("Hello");
}
}
C#
Type type = typeof(User);

Console.WriteLine(type.Name);

foreach (var property in type.GetProperties())
{
Console.WriteLine(property.Name);
}

foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}

GetProperties() でプロパティ一覧、GetMethods() でメソッド一覧を取得できます。

2-2. プロパティやフィールドの値を取得・変更する

リフレクションでは、プロパティの値を取得したり変更したりできます。

C#
var user = new User
{
Name = "Taro",
Age = 30
};

Type type = user.GetType();

var property = type.GetProperty("Name");
var value = property?.GetValue(user);

Console.WriteLine(value);

値を変更する場合は SetValue() を使います。

C#
property?.SetValue(user, "Hanako");

Console.WriteLine(user.Name);

このように、プロパティ名を文字列で指定して値を操作できます。

2-3. メソッドを文字列から動的に呼び出す

メソッド名を文字列で指定して実行することもできます。

C#
var user = new User();
Type type = user.GetType();

var method = type.GetMethod("SayHello");
method?.Invoke(user, null);

GetMethod() でメソッド情報を取得し、Invoke() で実行します。

引数があるメソッドの場合は、object[] で引数を渡します。

C#
public void Greet(string message)
{
Console.WriteLine(message);
}
C#
var method = typeof(User).GetMethod("Greet");
method?.Invoke(user, new object[] { "こんにちは" });

2-4. インスタンスを動的に生成する

Activator.CreateInstance() を使うと、型情報からインスタンスを生成できます。

C#
Type type = typeof(User);

var instance = Activator.CreateInstance(type);

生成されたオブジェクトは object 型として返されます。

C#
if (instance is User user)
{
user.Name = "Taro";
}

型が実行時までわからない場合や、設定ファイルに書かれたクラス名からインスタンスを生成したい場合などに使われます。

2-5. privateメンバーや属性情報にアクセスする

リフレクションでは、private なフィールドやメソッドにもアクセスできます。

C#
var field = typeof(User).GetField(
"_secret",
BindingFlags.NonPublic | BindingFlags.Instance
);

ただし、privateメンバーへのアクセスは本来のカプセル化を崩す行為です。テストやデバッグなど、限定的な用途にとどめるべきです。

また、クラスやプロパティに付与された属性も取得できます。

C#
var attributes = typeof(User).GetCustomAttributes(false);

属性を使うと、入力チェックや表示名の管理などを柔軟に行えます。

2-6. アセンブリ内の型情報を取得する

アセンブリとは、C#でビルドされたDLLやEXEの単位です。リフレクションを使うと、アセンブリ内に含まれる型を取得できます。

C#
var assembly = typeof(User).Assembly;

foreach (var type in assembly.GetTypes())
{
Console.WriteLine(type.FullName);
}

この仕組みは、DIコンテナの自動登録、プラグインの読み込み、特定の属性が付いたクラスの検索などで使われます。

3. C#リフレクションの基本的な使い方

3-1. GetType()でオブジェクトの型を取得する

GetType() は、インスタンスから型情報を取得するメソッドです。

C#
var user = new User();

Type type = user.GetType();

Console.WriteLine(type.Name);

実際に生成されたオブジェクトの型を調べたい場合に使います。

C#
object value = "Hello";

Console.WriteLine(value.GetType().Name);

この例では、変数の型は object ですが、実体は string なので String と表示されます。

3-2. typeof()でクラスの型情報を取得する

typeof() は、クラスや構造体などの型情報をコンパイル時に取得する構文です。

C#
Type type = typeof(User);

Console.WriteLine(type.FullName);

GetType() はインスタンスが必要ですが、typeof() はインスタンスがなくても使えます。

C#
Type stringType = typeof(string);
Type intType = typeof(int);
Type userType = typeof(User);

特定のクラスの型情報を取得したい場合は typeof()、手元にあるオブジェクトの実際の型を知りたい場合は GetType() を使うと覚えるとわかりやすいです。

3-3. GetProperties()でプロパティ一覧を取得する

GetProperties() を使うと、クラスのプロパティ一覧を取得できます。

C#
Type type = typeof(User);

foreach (var property in type.GetProperties())
{
Console.WriteLine(property.Name);
}

プロパティの型も取得できます。

C#
foreach (var property in type.GetProperties())
{
Console.WriteLine($"{property.Name}: {property.PropertyType.Name}");
}

実務では、オブジェクトの内容を一覧表示したり、CSVの列を自動生成したりするときに使えます。

3-4. GetMethods()でメソッド一覧を取得する

GetMethods() を使うと、メソッド一覧を取得できます。

C#
Type type = typeof(User);

foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}

ただし、GetMethods()ToString()Equals()GetHashCode() など、object クラス由来のメソッドも含めて取得します。

自分で定義したメソッドだけを対象にしたい場合は、条件で絞り込むことがあります。

C#
foreach (var method in type.GetMethods())
{
if (method.DeclaringType == typeof(User))
{
Console.WriteLine(method.Name);
}
}

3-5. GetProperty()とGetValue()でプロパティ値を取得する

特定のプロパティ値を取得するには、GetProperty()GetValue() を使います。

C#
var user = new User
{
Name = "Taro",
Age = 30
};

var property = typeof(User).GetProperty("Name");

if (property != null)
{
var value = property.GetValue(user);
Console.WriteLine(value);
}

GetProperty() は、指定した名前のプロパティが見つからない場合に null を返します。そのため、null チェックを行うことが重要です。

3-6. SetValue()でプロパティ値を変更する

プロパティの値を変更するには SetValue() を使います。

C#
var user = new User();

var property = typeof(User).GetProperty("Name");

if (property != null && property.CanWrite)
{
property.SetValue(user, "Hanako");
}

Console.WriteLine(user.Name);

CanWrite を確認すると、書き込み可能なプロパティかどうかを判断できます。

型が一致しない値を設定すると例外が発生します。

C#
var ageProperty = typeof(User).GetProperty("Age");

ageProperty?.SetValue(user, 25);

Ageint の場合、文字列の "25" をそのまま設定しようとするとエラーになります。必要に応じて型変換を行いましょう。

3-7. GetMethod()とInvoke()でメソッドを実行する

メソッドを動的に実行するには、GetMethod()Invoke() を使います。

C#
var user = new User();

var method = typeof(User).GetMethod("SayHello");

if (method != null)
{
method.Invoke(user, null);
}

引数付きメソッドの場合は、次のように書きます。

C#
var method = typeof(User).GetMethod("Greet");

if (method != null)
{
method.Invoke(user, new object[] { "こんにちは" });
}

戻り値があるメソッドの場合、Invoke() の戻り値を受け取れます。

C#
var result = method?.Invoke(user, new object[] { "こんにちは" });
Console.WriteLine(result);

4. 実例で学ぶC#リフレクション

4-1. サンプルクラスを用意する

ここからは、次の User クラスを使ってC#リフレクションの使い方を確認します。

C#
using System;

public class User
{
private string _secret = "秘密の値";

public string Name { get; set; } = "";
public int Age { get; set; }
public bool IsActive { get; set; }

public void SayHello()
{
Console.WriteLine($"こんにちは、{Name}です");
}

public void Greet(string message)
{
Console.WriteLine($"{Name}: {message}");
}

private void PrivateMethod()
{
Console.WriteLine("privateメソッドが呼び出されました");
}
}

このクラスには、publicプロパティ、publicメソッド、privateフィールド、privateメソッドが含まれています。

4-2. オブジェクトのプロパティ名と値を一覧表示する

リフレクションを使うと、オブジェクトのプロパティ名と値をまとめて取得できます。

C#
var user = new User
{
Name = "Taro",
Age = 30,
IsActive = true
};

Type type = user.GetType();

foreach (var property in type.GetProperties())
{
var value = property.GetValue(user);
Console.WriteLine($"{property.Name}: {value}");
}

実行結果の例は次のようになります。

Name: Taro
Age: 30
IsActive: True

このような処理は、ログ出力やデバッグに便利です。クラスごとに Console.WriteLine(user.Name) のようなコードを書かなくても、汎用的にプロパティを出力できます。

4-3. 文字列で指定したメソッドを実行する

次に、メソッド名を文字列で指定して実行します。

C#
var user = new User
{
Name = "Taro"
};

string methodName = "SayHello";

var method = typeof(User).GetMethod(methodName);

if (method != null)
{
method.Invoke(user, null);
}

引数付きメソッドを呼び出す場合は、次のようにします。

C#
string methodName = "Greet";

var method = typeof(User).GetMethod(methodName);

if (method != null)
{
method.Invoke(user, new object[] { "リフレクションの練習中です" });
}

このように、実行するメソッドを実行時に切り替えたい場合にリフレクションが役立ちます。

4-4. 条件に合うプロパティだけを取得する

プロパティの型を見て、条件に合うプロパティだけを取得することもできます。

たとえば、string 型のプロパティだけを取得する場合は次のように書きます。

C#
var properties = typeof(User).GetProperties();

foreach (var property in properties)
{
if (property.PropertyType == typeof(string))
{
Console.WriteLine(property.Name);
}
}

int 型のプロパティだけを取得する場合は次のようにします。

C#
foreach (var property in properties)
{
if (property.PropertyType == typeof(int))
{
Console.WriteLine(property.Name);
}
}

属性、型、名前、読み書き可能かどうかなど、さまざまな条件で絞り込めます。

C#
foreach (var property in properties)
{
if (property.CanRead && property.CanWrite)
{
Console.WriteLine($"{property.Name} は読み書き可能です");
}
}

4-5. カスタム属性を取得して処理を分岐する

C#リフレクションでは、カスタム属性を取得できます。

まず、独自の属性を作成します。

C#
[AttributeUsage(AttributeTargets.Property)]
public class DisplayNameAttribute : Attribute
{
public string Name { get; }

public DisplayNameAttribute(string name)
{
Name = name;
}
}

次に、プロパティに属性を付けます。

C#
public class Product
{
[DisplayName("商品名")]
public string Name { get; set; } = "";

[DisplayName("価格")]
public int Price { get; set; }
}

リフレクションで属性を取得します。

C#
Type type = typeof(Product);

foreach (var property in type.GetProperties())
{
var attribute = property
.GetCustomAttributes(typeof(DisplayNameAttribute), false)
.FirstOrDefault() as DisplayNameAttribute;

if (attribute != null)
{
Console.WriteLine($"{property.Name} の表示名は {attribute.Name} です");
}
}

この仕組みを使うと、画面表示名、CSVの列名、入力チェックルールなどを属性で管理できます。

4-6. privateメソッド・privateフィールドへアクセスする例

通常、privateメンバーはクラスの外からアクセスできません。しかし、リフレクションでは BindingFlags を指定することでアクセスできます。

privateフィールドを取得する例です。

C#
var user = new User();

var field = typeof(User).GetField(
"_secret",
BindingFlags.NonPublic | BindingFlags.Instance
);

if (field != null)
{
var value = field.GetValue(user);
Console.WriteLine(value);
}

privateメソッドを呼び出す例です。

C#
var method = typeof(User).GetMethod(
"PrivateMethod",
BindingFlags.NonPublic | BindingFlags.Instance
);

if (method != null)
{
method.Invoke(user, null);
}

ただし、privateメンバーへのアクセスは慎重に扱う必要があります。通常のアプリケーションコードで多用すると、設計が崩れやすくなります。

4-7. よくあるエラーと対処法:null、型不一致、メソッド未検出

リフレクションでよくあるエラーの1つは、GetProperty()GetMethod()null を返すケースです。

C#
var property = typeof(User).GetProperty("Unknown");

if (property == null)
{
Console.WriteLine("プロパティが見つかりません");
}

プロパティ名やメソッド名を文字列で指定するため、タイプミスに注意が必要です。

型不一致にも注意しましょう。

C#
var property = typeof(User).GetProperty("Age");

if (property != null)
{
property.SetValue(user, 30);
}

Ageint の場合、30 は設定できます。しかし、次のように文字列を渡すとエラーになる可能性があります。

C#
property?.SetValue(user, "30");

必要に応じて Convert.ChangeType() などで型変換します。

C#
var convertedValue = Convert.ChangeType("30", property.PropertyType);
property.SetValue(user, convertedValue);

また、引数付きメソッドを探す場合、オーバーロードがあると期待したメソッドが取得できないことがあります。その場合は、引数の型を指定して取得します。

C#
var method = typeof(User).GetMethod(
"Greet",
new[] { typeof(string) }
);

5. C#リフレクションのメリット

5-1. 汎用的な処理を書ける

リフレクションの大きなメリットは、型に依存しない汎用的な処理を書けることです。

たとえば、どのクラスでもプロパティ名と値を表示する処理を作れます。

C#
public static void PrintProperties(object obj)
{
Type type = obj.GetType();

foreach (var property in type.GetProperties())
{
var value = property.GetValue(obj);
Console.WriteLine($"{property.Name}: {value}");
}
}

このメソッドは、User でも Product でも Order でも使えます。

C#
PrintProperties(user);
PrintProperties(product);
PrintProperties(order);

クラスごとに専用の出力処理を書かなくてよいため、共通化しやすくなります。

5-2. コードの重複を減らせる

同じような処理を複数のクラスに対して書くと、コードの重複が増えます。

たとえば、ログ出力やCSV変換で次のようなコードを毎回書くのは手間です。

C#
Console.WriteLine(user.Name);
Console.WriteLine(user.Age);
Console.WriteLine(user.IsActive);

リフレクションを使えば、プロパティを自動的に列挙できるため、重複を減らせます。

C#
foreach (var property in user.GetType().GetProperties())
{
Console.WriteLine(property.GetValue(user));
}

クラスにプロパティが追加されても、処理を変更せずに対応できる場合があります。

5-3. 型が実行時までわからない場合に対応できる

実務では、実行時まで型が決まらないケースがあります。

たとえば、設定ファイルにクラス名を書いておき、そのクラスを動的に生成するようなケースです。

C#
Type? type = Type.GetType("MyApp.Services.UserService");

if (type != null)
{
var instance = Activator.CreateInstance(type);
}

プラグイン機構や拡張機能では、このように実行時に型を解決する仕組みが使われます。

5-4. DTO・エンティティ・設定クラスの処理を自動化できる

DTO、エンティティ、設定クラスなどは、プロパティを多く持つことがあります。

リフレクションを使うと、次のような処理を自動化しやすくなります。

C#
// プロパティの値を一覧表示する
// nullチェックをまとめて行う
// CSVの列に変換する
// 変更前後の値を比較する
// 設定値をオブジェクトに反映する

たとえば、すべての文字列プロパティが空でないか確認する処理も作れます。

C#
public static bool HasEmptyStringProperty(object obj)
{
foreach (var property in obj.GetType().GetProperties())
{
if (property.PropertyType == typeof(string))
{
var value = property.GetValue(obj) as string;

if (string.IsNullOrEmpty(value))
{
return true;
}
}
}

return false;
}

5-5. テスト・デバッグ・ログ出力に活用できる

リフレクションは、テストやデバッグにも役立ちます。

特に、オブジェクトの状態をまとめて確認したい場合に便利です。

C#
public static void Dump(object obj)
{
Console.WriteLine($"Type: {obj.GetType().Name}");

foreach (var property in obj.GetType().GetProperties())
{
Console.WriteLine($"{property.Name} = {property.GetValue(obj)}");
}
}

テストコードでは、通常アクセスできないprivateメンバーを確認したい場面もあります。ただし、privateメンバーのテストに頼りすぎると、実装詳細に依存したテストになりやすいため注意が必要です。

5-6. フレームワークやライブラリの内部処理を理解しやすくなる

C#の多くのフレームワークやライブラリでは、リフレクションに近い仕組みが使われています。

たとえば、次のような技術を理解しやすくなります。

C#
// ASP.NET CoreのDI
// Entity Framework Coreのエンティティ処理
// JSONシリアライザー
// テストフレームワーク
// 属性ベースのバリデーション
// Unityのコンポーネント処理

リフレクションを理解すると、「なぜ属性を付けるだけで処理が変わるのか」「なぜクラスを登録すると自動で生成されるのか」といった仕組みが見えやすくなります。

6. C#リフレクションの注意点・デメリット

6-1. 通常の呼び出しよりパフォーマンスが低下しやすい

リフレクションは、通常のメソッド呼び出しやプロパティアクセスよりも処理コストが高くなりやすいです。

通常の呼び出しは次のように直接実行できます。

C#
user.Name = "Taro";

一方、リフレクションでは型情報を調べ、プロパティを探し、値を設定します。

C#
var property = user.GetType().GetProperty("Name");
property?.SetValue(user, "Taro");

1回だけなら問題にならないことも多いですが、大量データをループで処理する場合は影響が出る可能性があります。

6-2. コンパイル時にエラーを検出しにくい

通常のコードでは、存在しないプロパティを使うとコンパイルエラーになります。

C#
user.UnknownProperty = "test";

しかし、リフレクションでは文字列で指定するため、コンパイル時には検出されません。

C#
var property = typeof(User).GetProperty("UnknownProperty");

このコードはコンパイルできますが、実行時にはプロパティが見つからず null になります。

そのため、リフレクションを使う場合は null チェックや例外処理が重要です。

6-3. 文字列指定によるタイプミスや変更漏れが起きやすい

リフレクションでは、プロパティ名やメソッド名を文字列で指定することが多いです。

C#
var property = typeof(User).GetProperty("Name");

この "Name""Nmae" と間違えても、コンパイルエラーにはなりません。

また、プロパティ名をリファクタリングで変更した場合、文字列までは自動で変更されないことがあります。

可能であれば、nameof を使うと安全性が上がります。

C#
var property = typeof(User).GetProperty(nameof(User.Name));

nameof を使えば、プロパティ名を変更したときに追従しやすくなります。

6-4. privateメンバーへのアクセスは設計上のリスクがある

リフレクションを使うと、privateフィールドやprivateメソッドにもアクセスできます。

しかし、privateメンバーは本来、クラスの外部から触られないことを前提に設計されています。

外部から無理にアクセスすると、次のような問題が起きやすくなります。

C#
// クラスの内部実装に依存する
// リファクタリングに弱くなる
// カプセル化が崩れる
// バグの原因が追いにくくなる

privateメンバーへのアクセスは、テストや移行作業、デバッグなど、限定的な用途にとどめるのが基本です。

6-5. セキュリティ面で注意が必要

リフレクションは強力な機能ですが、使い方によってはセキュリティリスクになります。

特に、外部入力をそのままクラス名やメソッド名として使う設計は危険です。

C#
string methodName = userInput;
var method = typeof(User).GetMethod(methodName);
method?.Invoke(user, null);

このようなコードでは、想定していないメソッドが呼び出される可能性があります。

外部入力を使う場合は、許可リストを作るなど、安全な範囲に限定しましょう。

C#
var allowedMethods = new[] { "SayHello", "Greet" };

if (allowedMethods.Contains(methodName))
{
var method = typeof(User).GetMethod(methodName);
method?.Invoke(user, null);
}

6-6. 可読性・保守性が下がる場合がある

リフレクションを多用すると、コードを読んだだけでは何が呼び出されるのかわかりにくくなります。

C#
var method = type.GetMethod(methodName);
method?.Invoke(instance, args);

このコードでは、methodName に何が入るかを追わないと、実際の処理がわかりません。

通常の呼び出しで十分な場合は、リフレクションを使わないほうが読みやすくなります。

C#
user.SayHello();

リフレクションは便利ですが、使いすぎると保守が難しくなる点に注意しましょう。

6-7. AOT・トリミング環境では動作に注意が必要

近年の.NETでは、AOTやトリミングを使ってアプリケーションサイズや起動速度を改善する場面があります。

ただし、リフレクションは実行時に型やメンバーを探すため、トリミングによって必要なメンバーが削除されると、期待どおりに動作しないことがあります。

たとえば、コード上で直接参照されていないプロパティやメソッドを、リフレクションだけで使っている場合は注意が必要です。

AOTやトリミングを使う環境では、リフレクションに依存しすぎない設計や、必要な型情報を保持する設定が必要になる場合があります。

7. C#リフレクションを使うべきケース・避けるべきケース

7-1. 使うべきケース:共通処理・動的処理・属性ベース処理

C#リフレクションは、次のようなケースで有効です。

C#
// 複数のクラスに共通する処理を書きたい
// 型が実行時まで決まらない
// 属性を読み取って処理を変えたい
// プラグインのように外部から型を読み込みたい
// DTOやエンティティを汎用的に処理したい

たとえば、すべてのプロパティをログ出力する処理や、カスタム属性に応じて入力チェックを行う処理は、リフレクションと相性がよいです。

7-2. 避けるべきケース:通常のメソッド呼び出しで十分な処理

通常のメソッド呼び出しで十分な場合は、リフレクションを使う必要はありません。

C#
user.SayHello();

このように書けるなら、わざわざ次のように書く必要はありません。

C#
var method = typeof(User).GetMethod("SayHello");
method?.Invoke(user, null);

通常の呼び出しのほうが、読みやすく、安全で、高速です。

リフレクションは「通常の書き方では対応しにくい場合」に使うものと考えるとよいでしょう。

7-3. 高頻度処理ではキャッシュを検討する

リフレクションをループ内で何度も実行すると、パフォーマンスに影響することがあります。

避けたい例です。

C#
foreach (var user in users)
{
var property = user.GetType().GetProperty("Name");
var value = property?.GetValue(user);
}

改善例として、PropertyInfo を先に取得して使い回します。

C#
var property = typeof(User).GetProperty("Name");

foreach (var user in users)
{
var value = property?.GetValue(user);
}

大量データを扱う場合は、TypePropertyInfoMethodInfo などをキャッシュすることを検討しましょう。

7-4. インターフェース・ジェネリック・デリゲートで代替できないか確認する

リフレクションを使う前に、他の方法で実現できないか確認することも大切です。

たとえば、共通の処理が必要ならインターフェースで表現できる場合があります。

C#
public interface IPrintable
{
void Print();
}

public class User : IPrintable
{
public void Print()
{
Console.WriteLine("Userを表示します");
}
}

この場合、リフレクションを使わなくても共通処理を書けます。

C#
void PrintObject(IPrintable printable)
{
printable.Print();
}

型に応じた処理を共通化したい場合は、ジェネリックも候補になります。

C#
public void Save<T>(T entity)
{
// 共通の保存処理
}

リフレクションは便利ですが、まずは通常のC#の機能で解決できるか検討しましょう。

7-5. 実務で判断するためのチェックリスト

C#リフレクションを使うかどうか迷ったら、次の観点で判断するとよいです。

C#
// 型やメンバー名は実行時まで決まらないか
// 複数のクラスに共通する処理か
// 属性を使った柔軟な制御が必要か
// 通常の呼び出しでは重複が増えすぎるか
// パフォーマンスへの影響は問題ないか
// 文字列指定による変更漏れを許容できるか
// privateメンバーに依存していないか
// AOTやトリミング環境で問題にならないか

すべての処理をリフレクションで書く必要はありません。柔軟性が必要な場所に限定して使うことが大切です。

8. C#リフレクションの実践的な活用例

8-1. オブジェクトの値をログ出力する

リフレクションの実践例として、オブジェクトのプロパティをログ出力する処理を作ってみます。

C#
public static class ObjectLogger
{
public static void Log(object obj)
{
if (obj == null)
{
Console.WriteLine("null");
return;
}

Type type = obj.GetType();

Console.WriteLine($"Type: {type.Name}");

foreach (var property in type.GetProperties())
{
if (!property.CanRead)
{
continue;
}

var value = property.GetValue(obj);
Console.WriteLine($"{property.Name}: {value}");
}
}
}

使い方です。

C#
var user = new User
{
Name = "Taro",
Age = 30,
IsActive = true
};

ObjectLogger.Log(user);

このような処理は、デバッグ時やエラー調査時に便利です。

8-2. CSV・JSON変換処理でプロパティを動的に扱う

CSV変換では、プロパティ一覧を列として扱うことがあります。

C#
public static string ToCsvHeader<T>()
{
var properties = typeof(T).GetProperties();

return string.Join(",", properties.Select(p => p.Name));
}

値をCSV行にする例です。

C#
public static string ToCsvRow<T>(T obj)
{
var properties = typeof(T).GetProperties();

var values = properties.Select(p => p.GetValue(obj)?.ToString() ?? "");

return string.Join(",", values);
}

使い方です。

C#
var user = new User
{
Name = "Taro",
Age = 30,
IsActive = true
};

Console.WriteLine(ToCsvHeader<User>());
Console.WriteLine(ToCsvRow(user));

実務では、カンマや改行、ダブルクォートのエスケープ処理も必要ですが、リフレクションを使うことでプロパティを動的に扱えることがわかります。

8-3. カスタム属性を使って入力チェックを行う

カスタム属性とリフレクションを組み合わせると、入力チェックを柔軟に実装できます。

まず、必須項目を表す属性を作ります。

C#
[AttributeUsage(AttributeTargets.Property)]
public class RequiredAttribute : Attribute
{
}

クラスに属性を付けます。

C#
public class UserInput
{
[Required]
public string Name { get; set; } = "";

public string Email { get; set; } = "";
}

入力チェック処理です。

C#
public static List<string> Validate(object obj)
{
var errors = new List<string>();

foreach (var property in obj.GetType().GetProperties())
{
bool isRequired = property
.GetCustomAttributes(typeof(RequiredAttribute), false)
.Any();

if (!isRequired)
{
continue;
}

var value = property.GetValue(obj);

if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
{
errors.Add($"{property.Name} は必須です");
}
}

return errors;
}

使い方です。

C#
var input = new UserInput
{
Name = "",
Email = "test@example.com"
};

var errors = Validate(input);

foreach (var error in errors)
{
Console.WriteLine(error);
}

このように、属性を付けるだけでチェック対象を制御できます。

8-4. DIコンテナやORMでのリフレクション活用イメージ

DIコンテナでは、クラスのコンストラクタを調べて必要な依存関係を解決する場面があります。

イメージとしては、次のような処理です。

C#
Type type = typeof(UserService);

var constructor = type.GetConstructors().First();

var parameters = constructor.GetParameters();

foreach (var parameter in parameters)
{
Console.WriteLine($"{parameter.Name}: {parameter.ParameterType.Name}");
}

ORMでは、エンティティのプロパティを調べて、データベースのカラムと対応付けることがあります。

C#
Type type = typeof(User);

foreach (var property in type.GetProperties())
{
Console.WriteLine($"Column: {property.Name}");
}

実際のDIコンテナやORMはより高度な処理を行いますが、基本には型情報や属性情報を扱う考え方があります。

8-5. Unity開発でのリフレクション活用例

Unity開発でも、リフレクションが使われる場面があります。

たとえば、特定の属性が付いたメソッドを探して実行する、デバッグ用にコンポーネントの値を表示する、エディタ拡張でプロパティを動的に扱う、といった用途です。

C#
var component = someGameObject.GetComponent<MyComponent>();

foreach (var property in component.GetType().GetProperties())
{
Console.WriteLine(property.Name);
}

ただし、Unityではパフォーマンスが重要な場面も多いため、Update内で毎フレームリフレクションを使うような実装は避けたほうがよいです。

必要な情報は初期化時に取得してキャッシュするなど、使い方を工夫しましょう。

9. C#リフレクションのパフォーマンス対策

9-1. Type・PropertyInfo・MethodInfoをキャッシュする

リフレクションのパフォーマンス対策として基本になるのがキャッシュです。

毎回 GetProperty() を呼び出すのではなく、取得した PropertyInfo を使い回します。

C#
var property = typeof(User).GetProperty("Name");

foreach (var user in users)
{
var value = property?.GetValue(user);
}

複数の型を扱う場合は、辞書にキャッシュする方法もあります。

C#
private static readonly Dictionary<Type, PropertyInfo[]> Cache = new();

public static PropertyInfo[] GetCachedProperties(Type type)
{
if (!Cache.TryGetValue(type, out var properties))
{
properties = type.GetProperties();
Cache[type] = properties;
}

return properties;
}

頻繁に使う型情報はキャッシュしておくと、無駄な探索を減らせます。

9-2. ループ内で何度もリフレクションを実行しない

大量データを扱う場合、ループの中で何度もリフレクションを実行しないようにしましょう。

避けたい例です。

C#
foreach (var item in items)
{
foreach (var property in item.GetType().GetProperties())
{
Console.WriteLine(property.GetValue(item));
}
}

改善例です。

C#
var properties = typeof(User).GetProperties();

foreach (var user in users)
{
foreach (var property in properties)
{
Console.WriteLine(property.GetValue(user));
}
}

型が同じであれば、プロパティ一覧は毎回取得する必要がありません。

9-3. Expression TreeやDelegate化を検討する

リフレクションで取得したメソッドやプロパティを何度も呼び出す場合、Expression TreeやDelegate化を検討できます。

たとえば、メソッドをデリゲートとして取得できれば、Invoke() よりも効率よく呼び出せる場合があります。

C#
var method = typeof(User).GetMethod("SayHello");

if (method != null)
{
var action = (Action<User>)Delegate.CreateDelegate(
typeof(Action<User>),
method
);

action(new User { Name = "Taro" });
}

ただし、Expression TreeやDelegate化はコードが複雑になりやすいです。パフォーマンス上の問題が実際に確認できてから検討するのが現実的です。

9-4. 計測してから最適化する

リフレクションは遅いと言われることがありますが、すべてのケースで問題になるわけではありません。

たとえば、アプリケーション起動時に一度だけ型情報を読み取る程度であれば、大きな問題にならないことも多いです。

一方で、数十万件のデータを変換する処理や、毎フレーム実行される処理では影響が出る可能性があります。

重要なのは、推測ではなく計測することです。

C#
var stopwatch = Stopwatch.StartNew();

// 計測したい処理

stopwatch.Stop();

Console.WriteLine(stopwatch.ElapsedMilliseconds);

実際に遅い部分を確認してから最適化しましょう。

9-5. パフォーマンスより柔軟性を優先してよい場面

リフレクションはパフォーマンスに注意が必要ですが、柔軟性を優先してよい場面もあります。

たとえば、次のような処理です。

C#
// 管理画面の設定読み込み
// アプリ起動時の初期化
// テスト補助
// デバッグ用ログ
// 開発ツール
// 少量データの変換

高頻度で実行されない処理であれば、リフレクションの柔軟性が大きなメリットになることがあります。

10. C#リフレクションでよく使うクラス・メソッド一覧

10-1. Type

Type は、型情報を表す中心的なクラスです。

C#
Type type = typeof(User);

よく使うメンバーです。

C#
type.Name
type.FullName
type.Namespace
type.GetProperties()
type.GetMethods()
type.GetFields()
type.GetConstructors()
type.GetCustomAttributes(false)

リフレクションでは、まず Type を取得することが多いです。

10-2. PropertyInfo

PropertyInfo は、プロパティ情報を表します。

C#
var property = typeof(User).GetProperty("Name");

よく使うメンバーです。

C#
property.Name
property.PropertyType
property.CanRead
property.CanWrite
property.GetValue(obj)
property.SetValue(obj, value)
property.GetCustomAttributes(false)

プロパティ値の取得や変更に使います。

10-3. FieldInfo

FieldInfo は、フィールド情報を表します。

C#
var field = typeof(User).GetField(
"_secret",
BindingFlags.NonPublic | BindingFlags.Instance
);

よく使うメンバーです。

C#
field.Name
field.FieldType
field.GetValue(obj)
field.SetValue(obj, value)

publicフィールドだけでなく、BindingFlags を使えばprivateフィールドも取得できます。

10-4. MethodInfo

MethodInfo は、メソッド情報を表します。

C#
var method = typeof(User).GetMethod("SayHello");

よく使うメンバーです。

C#
method.Name
method.ReturnType
method.GetParameters()
method.Invoke(obj, args)

メソッドを動的に実行する場合に使います。

10-5. ConstructorInfo

ConstructorInfo は、コンストラクタ情報を表します。

C#
var constructors = typeof(User).GetConstructors();

コンストラクタの引数情報を取得できます。

C#
foreach (var constructor in constructors)
{
foreach (var parameter in constructor.GetParameters())
{
Console.WriteLine(parameter.ParameterType.Name);
}
}

DIコンテナのように、コンストラクタを調べてインスタンス生成する処理で使われます。

10-6. Attribute

Attribute は、クラスやプロパティ、メソッドなどに付与する追加情報です。

C#
[Obsolete]
public void OldMethod()
{
}

カスタム属性も作成できます。

C#
public class MyAttribute : Attribute
{
}

リフレクションで属性を取得することで、属性に応じた処理分岐ができます。

C#
var attributes = typeof(User).GetCustomAttributes(false);

10-7. BindingFlags

BindingFlags は、リフレクションで取得するメンバーの条件を指定するために使います。

代表的な値です。

C#
BindingFlags.Public
BindingFlags.NonPublic
BindingFlags.Instance
BindingFlags.Static
BindingFlags.DeclaredOnly

privateメソッドを取得する例です。

C#
var method = typeof(User).GetMethod(
"PrivateMethod",
BindingFlags.NonPublic | BindingFlags.Instance
);

staticメソッドを取得する場合は、BindingFlags.Static を使います。

C#
var method = typeof(SomeClass).GetMethod(
"StaticMethod",
BindingFlags.Public | BindingFlags.Static
);

11. C#リフレクションのよくある質問

11-1. リフレクションは初心者でも覚えるべき?

C#初心者が最初から深く覚える必要はありません。

まずは、クラス、メソッド、プロパティ、インターフェース、ジェネリックなど、基本的なC#の機能を理解することが大切です。

ただし、リフレクションの基本概念を知っておくと、フレームワークやライブラリの仕組みを理解しやすくなります。

最初は次の3つを覚えるだけでも十分です。

C#
GetType()
typeof()
GetProperties()

オブジェクトの型情報を取得し、プロパティ一覧を表示できるようになれば、リフレクションの入口としては十分です。

11-2. リフレクションは遅いから使わないほうがよい?

リフレクションは通常の呼び出しより遅くなりやすいですが、使ってはいけないわけではありません。

重要なのは、使う場所です。

アプリケーション起動時に一度だけ実行する処理や、管理画面、ログ出力、少量データの変換であれば、問題にならないことも多いです。

一方、大量データのループ処理や、リアルタイム性が重要な処理では注意が必要です。

高頻度で使う場合は、PropertyInfoMethodInfo をキャッシュする、デリゲート化する、別の設計に変えるなどを検討しましょう。

11-3. privateメンバーにアクセスしてもよい?

技術的には可能ですが、通常のアプリケーションコードではおすすめしません。

privateメンバーは、クラスの内部実装として隠されているものです。外部からアクセスすると、カプセル化が崩れ、保守性が下がります。

ただし、次のような限定的な用途では使われることがあります。

C#
// テストコード
// デバッグ
// 移行作業
// レガシーコード対応
// フレームワーク内部処理

基本的には、publicメソッドやpublicプロパティを通して操作できる設計を優先しましょう。

11-4. GetType()とtypeof()の違いは?

GetType() は、インスタンスから実際の型を取得します。

C#
var user = new User();

Type type = user.GetType();

一方、typeof() は、型そのものから型情報を取得します。

C#
Type type = typeof(User);

違いをまとめると、次のようになります。

C#
// インスタンスがある場合
user.GetType()

// インスタンスがなくても使える
typeof(User)

また、ポリモーフィズムが関係する場合、GetType() は実際の実行時の型を返します。

C#
object value = "Hello";

Console.WriteLine(value.GetType()); // System.String

変数の宣言型ではなく、実体の型を知りたいときは GetType() を使います。

11-5. Invoke()で引数付きメソッドを呼び出すには?

引数付きメソッドを呼び出すには、Invoke() の第2引数に object[] を渡します。

C#
public class User
{
public void Greet(string message)
{
Console.WriteLine(message);
}
}
C#
var user = new User();

var method = typeof(User).GetMethod("Greet");

method?.Invoke(user, new object[] { "こんにちは" });

複数の引数がある場合は、順番に並べます。

C#
public void AddScore(string name, int score)
{
Console.WriteLine($"{name}: {score}");
}
C#
var method = typeof(User).GetMethod("AddScore");

method?.Invoke(user, new object[] { "Taro", 100 });

引数の数や型が合わないと例外が発生するため注意しましょう。

11-6. リフレクションと動的型付けdynamicの違いは?

リフレクションと dynamic はどちらも実行時に処理を決められる機能ですが、目的や使い方が異なります。

dynamic を使うと、コンパイル時の型チェックを遅らせて、実行時にメンバーを解決します。

C#
dynamic user = new User();
user.SayHello();

一方、リフレクションは型情報を明示的に取得して、プロパティやメソッドを操作します。

C#
var method = typeof(User).GetMethod("SayHello");
method?.Invoke(user, null);

dynamic はコードを簡潔に書ける反面、実行時エラーに注意が必要です。リフレクションは記述量が増えますが、型情報や属性情報を細かく扱えます。

属性を取得したい、プロパティ一覧を列挙したい、privateメンバーにアクセスしたいといった場合はリフレクションを使います。

まとめ

C#リフレクションとは、実行時に型情報を調べたり、プロパティやメソッドを動的に操作したりできる仕組みです。

リフレクションを使うと、クラス名、メソッド名、プロパティ名の取得、プロパティ値の取得・変更、メソッドの動的実行、インスタンス生成、属性情報の取得、アセンブリ内の型探索などができます。

基本的な流れは、まず GetType()typeof()Type を取得し、そこから GetProperties()GetMethods()GetProperty()GetMethod() などを使ってメンバー情報を取得する形です。

C#リフレクションには、汎用的な処理を書ける、コードの重複を減らせる、実行時まで型がわからないケースに対応できる、DTOやエンティティの処理を自動化できるといったメリットがあります。

一方で、通常の呼び出しより遅くなりやすい、コンパイル時にエラーを検出しにくい、文字列指定によるタイプミスが起きやすい、privateメンバーへのアクセスは設計上のリスクがある、といった注意点もあります。

実務では、通常のメソッド呼び出し、インターフェース、ジェネリック、デリゲートなどで解決できる場合はそちらを優先し、リフレクションは共通処理、動的処理、属性ベース処理などに限定して使うのがよいでしょう。

C#リフレクションは、正しく使えば非常に便利な機能です。まずは GetType()typeof()GetProperties()GetValue()SetValue()GetMethod()Invoke() から試して、少しずつ活用範囲を広げていきましょう。