C#リフレクションとは?できること・使い方・注意点を初心者向けにわかりやすく解説
はじめに
C#で開発していると、「クラスのプロパティを一覧で取得したい」「文字列で指定したメソッドを実行したい」「属性情報を読み取って共通処理を作りたい」といった場面があります。
このようなときに使われる仕組みがリフレクションです。
C#リフレクションは、英語では「C# Reflection」や「csharp reflection」と呼ばれ、実行中のプログラムから型情報を調べたり、オブジェクトのメンバーに動的にアクセスしたりできる機能です。
便利な一方で、通常のC#コードより遅くなりやすい、実行時エラーが起きやすい、設計を複雑にしやすいといった注意点もあります。
この記事では、C#リフレクションとは何か、できること、基本的な使い方、実践コード例、注意点、パフォーマンス対策まで初心者向けにわかりやすく解説します。
1. C#リフレクションとは?初心者向けにわかりやすく解説
1-1. リフレクションは「実行時に型情報を調べて操作する仕組み」
C#のリフレクションとは、プログラムの実行時に、クラス・プロパティ・メソッド・フィールド・属性などの情報を調べたり操作したりする仕組みです。
通常、C#では次のようにコンパイル時に型やメンバーが決まっています。
C#var user = new User();
user.Name = "Taro";
Console.WriteLine(user.Name);
このコードでは、UserクラスにNameプロパティがあることをコンパイラが事前にチェックします。
一方、リフレクションを使うと、次のように文字列でプロパティ名を指定して値を取得できます。
C#var user = new User { Name = "Taro" };
var type = user.GetType();
var property = type.GetProperty("Name");
var value = property?.GetValue(user);
Console.WriteLine(value);
このように、実行時に型情報を調べて操作できるのがリフレクションです。
1-2. 通常のC#コードとリフレクションの違い
通常のC#コードでは、呼び出すクラスやメソッドをコード上に直接書きます。
C#user.Name = "Taro";
user.SayHello();
この書き方はシンプルで高速です。また、存在しないプロパティやメソッドを呼び出そうとすると、コンパイル時にエラーになります。
一方、リフレクションでは次のように型名やメンバー名を文字列で扱います。
C#var property = typeof(User).GetProperty("Name");
property?.SetValue(user, "Taro");
var method = typeof(User).GetMethod("SayHello");
method?.Invoke(user, null);
リフレクションは柔軟ですが、"Name"や"SayHello"のような文字列を間違えてもコンパイル時には検出されません。実行して初めてエラーになったり、nullが返ったりします。
つまり、通常のC#コードは安全で速い、リフレクションは柔軟だが慎重に使う必要があるという違いがあります。
1-3. リフレクションを理解するために必要な「型」「メタデータ」「アセンブリ」の基礎
リフレクションを理解するには、次の3つの用語を押さえておくとわかりやすくなります。
型とは、クラス、構造体、インターフェース、列挙型などの情報のことです。C#ではTypeクラスを使って型情報を扱います。
C#Type type = typeof(string);
Console.WriteLine(type.Name);
メタデータとは、プログラムに含まれる型やメンバーに関する情報です。たとえば、クラス名、プロパティ名、メソッド名、引数、戻り値、属性などがメタデータです。
アセンブリとは、C#のプログラムをビルドした結果として生成される.dllや.exeのことです。アセンブリには、実行コードだけでなく、リフレクションで読み取れるメタデータも含まれています。
リフレクションは、このメタデータを実行時に読み取ることで、型情報の取得やメソッドの呼び出しを実現しています。
1-4. System.Reflection名前空間の役割
C#でリフレクションを使うときは、主にSystem.Reflection名前空間を利用します。
C#using System.Reflection;
System.Reflectionには、次のようなクラスが含まれています。
C#Type type = typeof(User);
PropertyInfo[] properties = type.GetProperties();
MethodInfo[] methods = type.GetMethods();
FieldInfo[] fields = type.GetFields();
代表的なクラスは次のとおりです。
| クラス | 役割 |
|---|---|
Type | 型情報を表す |
PropertyInfo | プロパティ情報を表す |
MethodInfo | メソッド情報を表す |
FieldInfo | フィールド情報を表す |
ConstructorInfo | コンストラクタ情報を表す |
Assembly | アセンブリ情報を表す |
BindingFlags | 取得対象を細かく指定する |
C#リフレクションでは、まずTypeを取得し、そこからプロパティ、メソッド、フィールドなどを調べる流れが基本です。
2. C#リフレクションでできること
2-1. クラス名・プロパティ名・メソッド名などの型情報を取得する
リフレクションを使うと、クラス名やプロパティ名、メソッド名などを実行時に取得できます。
C#Type type = typeof(User);
Console.WriteLine(type.Name);
Console.WriteLine(type.FullName);
foreach (var property in type.GetProperties())
{
Console.WriteLine(property.Name);
}
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
この機能は、汎用的なログ出力、デバッグツール、オブジェクトの自動変換処理などでよく使われます。
2-2. プロパティやフィールドの値を動的に取得・変更する
リフレクションでは、プロパティ名やフィールド名を文字列で指定して値を取得・変更できます。
C#var user = new User { Name = "Taro" };
PropertyInfo? property = typeof(User).GetProperty("Name");
var value = property?.GetValue(user);
Console.WriteLine(value);
property?.SetValue(user, "Hanako");
Console.WriteLine(user.Name);
通常はuser.Nameのように直接アクセスしますが、リフレクションを使えばプロパティ名を外部設定や文字列から動的に決めることができます。
2-3. メソッドを文字列で指定して動的に実行する
メソッド名を文字列で指定して呼び出すこともできます。
C#var user = new User();
MethodInfo? method = typeof(User).GetMethod("SayHello");
method?.Invoke(user, null);
引数があるメソッドの場合は、Invokeの第2引数に配列で値を渡します。
C#MethodInfo? method = typeof(User).GetMethod("Greet");
method?.Invoke(user, new object[] { "Taro" });
プラグイン機構やコマンド実行のように、実行する処理を実行時に決めたい場合に使われます。
2-4. インスタンスを実行時に生成する
リフレクションでは、Activator.CreateInstanceを使って実行時にインスタンスを生成できます。
C#Type type = typeof(User);
object? instance = Activator.CreateInstance(type);
型がコンパイル時に確定していなくても、Typeが取得できればインスタンスを作れます。
C#Type? type = Type.GetType("MyApp.User");
if (type != null)
{
object? instance = Activator.CreateInstance(type);
}
DIコンテナやプラグインの読み込みなど、実行時に型を決定する仕組みで使われます。
2-5. 属性(Attribute)の情報を取得する
C#では、クラスやプロパティ、メソッドに属性を付けられます。
C#[Obsolete("このクラスは古いです")]
public class OldService
{
}
リフレクションを使うと、この属性情報を実行時に取得できます。
C#var attributes = typeof(OldService).GetCustomAttributes(false);
foreach (var attribute in attributes)
{
Console.WriteLine(attribute.GetType().Name);
}
属性とリフレクションを組み合わせると、設定情報をコードに埋め込み、その情報をもとに処理を切り替えることができます。
2-6. privateメンバーやinternalクラスへアクセスする
BindingFlagsを使うと、通常は外部からアクセスできないprivateメンバーも取得できます。
C#var field = typeof(User).GetField(
"_password",
BindingFlags.Instance | BindingFlags.NonPublic
);
また、アセンブリからinternalクラスを取得することもできます。
C#var assembly = typeof(User).Assembly;
var type = assembly.GetType("MyApp.InternalService");
ただし、privateメンバーやinternalクラスへのアクセスは、本来隠されている実装詳細に依存することになります。テストやデバッグなど限定的な用途を除き、安易に使うべきではありません。
2-7. プラグイン・DI・シリアライズなどの仕組みに活用する
C#リフレクションは、さまざまなフレームワークやライブラリの内部で使われています。
たとえば、JSONシリアライザーはオブジェクトのプロパティ一覧を取得してJSONに変換します。DIコンテナはコンストラクタ情報を調べて必要な依存関係を注入します。ORMはクラスのプロパティとデータベースのカラムを対応付けます。
つまり、リフレクションはアプリケーションコードで頻繁に書くものではありませんが、フレームワークや汎用ライブラリを支える重要な仕組みです。
3. C#リフレクションの基本的な使い方
3-1. GetTypeでオブジェクトの型を取得する
GetTypeは、オブジェクトから実際の型情報を取得するメソッドです。
C#var user = new User();
Type type = user.GetType();
Console.WriteLine(type.Name);
GetTypeは実行時の実際の型を取得します。
C#object value = "Hello";
Console.WriteLine(value.GetType().Name);
この場合、変数の型はobjectですが、実際の中身はstringなのでStringと表示されます。
3-2. typeofでクラスの型情報を取得する
typeofは、コード上で指定した型のTypeを取得します。
C#Type type = typeof(User);
Console.WriteLine(type.FullName);
GetTypeはオブジェクトが必要ですが、typeofはインスタンスを作らなくても型情報を取得できます。
C#Type stringType = typeof(string);
Type intType = typeof(int);
Type listType = typeof(List<string>);
型がコンパイル時にわかっている場合は、typeofを使うのが一般的です。
3-3. Type.GetTypeで文字列から型を取得する
Type.GetTypeを使うと、文字列から型を取得できます。
C#Type? type = Type.GetType("System.String");
Console.WriteLine(type?.Name);
自作クラスを取得する場合は、名前空間を含めた完全修飾名が必要です。
C#Type? type = Type.GetType("MyApp.Models.User");
ただし、別アセンブリにある型を取得する場合は、アセンブリ名も必要になることがあります。
C#Type? type = Type.GetType("MyApp.Models.User, MyApp");
Type.GetTypeは取得できない場合にnullを返すため、必ずnullチェックをしましょう。
3-4. GetPropertiesでプロパティ一覧を取得する
GetPropertiesを使うと、型に定義されているプロパティ一覧を取得できます。
C#Type type = typeof(User);
PropertyInfo[] properties = type.GetProperties();
foreach (var property in properties)
{
Console.WriteLine(property.Name);
}
プロパティの型も取得できます。
C#foreach (var property in properties)
{
Console.WriteLine($"{property.Name}: {property.PropertyType.Name}");
}
オブジェクトの中身を一覧表示したい場合などに便利です。
3-5. GetMethodsでメソッド一覧を取得する
GetMethodsを使うと、メソッド一覧を取得できます。
C#Type type = typeof(User);
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
{
Console.WriteLine(method.Name);
}
GetMethodsでは、ToStringやEqualsなど、objectから継承されたメソッドも含まれます。
自分で定義したメソッドだけを取得したい場合は、BindingFlags.DeclaredOnlyを使います。
C#var methods = typeof(User).GetMethods(
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.DeclaredOnly
);
3-6. GetFieldsでフィールド一覧を取得する
GetFieldsを使うと、フィールド一覧を取得できます。
C#Type type = typeof(User);
FieldInfo[] fields = type.GetFields();
foreach (var field in fields)
{
Console.WriteLine(field.Name);
}
ただし、privateフィールドは通常のGetFields()では取得できません。取得したい場合はBindingFlagsを指定します。
C#var fields = typeof(User).GetFields(
BindingFlags.Instance |
BindingFlags.NonPublic
);
プロパティとフィールドは別物です。自動実装プロパティの裏側にはフィールドがありますが、通常はプロパティを扱うほうが安全です。
3-7. BindingFlagsで取得対象を細かく指定する
BindingFlagsは、リフレクションで取得する対象を細かく指定するための列挙型です。
C#var properties = typeof(User).GetProperties(
BindingFlags.Instance |
BindingFlags.Public
);
よく使う指定は次のとおりです。
| BindingFlags | 意味 |
|---|---|
Public | publicメンバーを対象にする |
NonPublic | privateやprotectedなど非公開メンバーを対象にする |
Instance | インスタンスメンバーを対象にする |
Static | staticメンバーを対象にする |
DeclaredOnly | その型で宣言されたメンバーだけを対象にする |
IgnoreCase | 大文字小文字を区別しない |
privateメンバーを取得する例です。
C#var field = typeof(User).GetField(
"_name",
BindingFlags.Instance | BindingFlags.NonPublic
);
staticメソッドを取得する例です。
C#var method = typeof(User).GetMethod(
"CreateDefault",
BindingFlags.Static | BindingFlags.Public
);
BindingFlagsを正しく使うことで、取得できない原因を減らせます。
4. C#リフレクションの実践コード例
4-1. クラスのプロパティ一覧を取得するサンプル
まずはサンプル用のクラスを用意します。
C#public class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Age { get; set; }
}
プロパティ一覧を取得するコードです。
C#Type type = typeof(User);
foreach (var property in type.GetProperties())
{
Console.WriteLine($"Name: {property.Name}, Type: {property.PropertyType.Name}");
}
実行結果の例です。
Name: Id, Type: Int32
Name: Name, Type: String
Name: Age, Type: Int32
このように、クラスのプロパティ名や型を実行時に取得できます。
4-2. プロパティ名を指定して値を取得するサンプル
プロパティ名を文字列で指定して値を取得します。
C#var user = new User
{
Id = 1,
Name = "Taro",
Age = 25
};
PropertyInfo? property = typeof(User).GetProperty("Name");
if (property != null)
{
object? value = property.GetValue(user);
Console.WriteLine(value);
}
実行結果です。
Taro
GetPropertyは、指定した名前のプロパティが存在しない場合にnullを返します。必ずnullチェックを入れましょう。
4-3. プロパティの値を動的に変更するサンプル
SetValueを使うと、プロパティの値を変更できます。
C#var user = new User
{
Id = 1,
Name = "Taro",
Age = 25
};
PropertyInfo? property = typeof(User).GetProperty("Name");
if (property != null && property.CanWrite)
{
property.SetValue(user, "Hanako");
}
Console.WriteLine(user.Name);
実行結果です。
Hanako
CanWriteを確認すると、読み取り専用プロパティに値を設定しようとしてエラーになるのを防げます。
型が合わない値を設定すると例外が発生します。
C#property.SetValue(user, 123);
Nameはstring型なので、intを設定しようとするとエラーになります。
4-4. メソッド名を文字列で指定して実行するサンプル
メソッドを持つクラスを用意します。
C#public class UserService
{
public void SayHello()
{
Console.WriteLine("Hello!");
}
}
リフレクションでメソッドを実行します。
C#var service = new UserService();
MethodInfo? method = typeof(UserService).GetMethod("SayHello");
method?.Invoke(service, null);
実行結果です。
Hello!
Invokeの第1引数には対象インスタンス、第2引数には引数の配列を指定します。引数がない場合はnullで問題ありません。
4-5. 引数ありメソッドをInvokeで呼び出すサンプル
引数ありメソッドを用意します。
C#public class UserService
{
public void Greet(string name)
{
Console.WriteLine($"Hello, {name}!");
}
}
リフレクションで呼び出します。
C#var service = new UserService();
MethodInfo? method = typeof(UserService).GetMethod("Greet");
method?.Invoke(service, new object[] { "Taro" });
実行結果です。
Hello, Taro!
戻り値があるメソッドの場合は、Invokeの戻り値で受け取れます。
C#public class Calculator
{
public int Add(int x, int y)
{
return x + y;
}
}
C#var calculator = new Calculator();
MethodInfo? method = typeof(Calculator).GetMethod("Add");
object? result = method?.Invoke(calculator, new object[] { 10, 20 });
Console.WriteLine(result);
実行結果です。
30
4-6. Activator.CreateInstanceでインスタンスを生成するサンプル
Activator.CreateInstanceを使うと、Typeからインスタンスを作れます。
C#Type type = typeof(User);
object? instance = Activator.CreateInstance(type);
Console.WriteLine(instance?.GetType().Name);
引数ありコンストラクタを呼び出すこともできます。
C#public class Product
{
public string Name { get; }
public Product(string name)
{
Name = name;
}
}
C#Type type = typeof(Product);
object? instance = Activator.CreateInstance(type, "Book");
Console.WriteLine(((Product)instance!).Name);
実行結果です。
Book
ただし、コンストラクタの引数が合わない場合は例外が発生します。型や引数の数に注意しましょう。
4-7. カスタム属性を取得するサンプル
カスタム属性を定義します。
C#[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
public class DisplayNameAttribute : Attribute
{
public string Name { get; }
public DisplayNameAttribute(string name)
{
Name = name;
}
}
クラスとプロパティに属性を付けます。
C#[DisplayName("ユーザー")]
public class User
{
[DisplayName("ユーザーID")]
public int Id { get; set; }
[DisplayName("名前")]
public string Name { get; set; } = "";
}
クラスの属性を取得します。
C#var classAttribute = typeof(User)
.GetCustomAttribute<DisplayNameAttribute>();
Console.WriteLine(classAttribute?.Name);
プロパティの属性を取得します。
C#foreach (var property in typeof(User).GetProperties())
{
var attribute = property.GetCustomAttribute<DisplayNameAttribute>();
if (attribute != null)
{
Console.WriteLine($"{property.Name}: {attribute.Name}");
}
}
実行結果の例です。
ユーザー
Id: ユーザーID
Name: 名前
属性とリフレクションを組み合わせると、表示名、バリデーション、マッピング設定などを柔軟に扱えます。
5. C#リフレクションが使われる代表的な場面
5-1. JSONシリアライズ・デシリアライズ
JSONシリアライズでは、オブジェクトのプロパティを読み取り、JSON文字列に変換します。
C#var user = new User
{
Id = 1,
Name = "Taro"
};
このようなオブジェクトをJSONにすると、プロパティ名と値が対応付けられます。
JSON{
"id": 1,
"name": "Taro"
}
多くのシリアライザーでは、プロパティ情報や属性情報を使って変換ルールを判断します。
たとえば、プロパティに属性を付けてJSON上の名前を変えるような仕組みは、リフレクションと相性がよい代表例です。
5-2. DIコンテナによる依存関係の解決
DIコンテナは、クラスのコンストラクタを調べて必要な依存関係を自動的に注入します。
C#public class UserService
{
private readonly IUserRepository _repository;
public UserService(IUserRepository repository)
{
_repository = repository;
}
}
DIコンテナは、UserServiceのコンストラクタにIUserRepositoryが必要であることを調べ、登録済みの実装クラスを渡してインスタンスを生成します。
このような仕組みの内部では、コンストラクタ情報や引数の型情報を調べるためにリフレクションが使われます。
5-3. ORMでのデータベースマッピング
ORMは、C#のクラスとデータベースのテーブルを対応付ける仕組みです。
C#public class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
ORMでは、UserクラスのIdやNameをデータベースのカラムに対応付けます。
リフレクションを使うことで、クラスのプロパティを読み取り、SQLの結果をオブジェクトに設定したり、オブジェクトの値をSQLに変換したりできます。
5-4. 単体テストやデバッグでの内部状態確認
単体テストやデバッグでは、privateフィールドの値を確認したい場面があります。
C#var field = typeof(UserService).GetField(
"_cache",
BindingFlags.Instance | BindingFlags.NonPublic
);
ただし、テストでprivateメンバーに依存しすぎると、内部実装を少し変えただけでテストが壊れやすくなります。
基本的にはpublicな振る舞いをテストし、どうしても必要な場合だけリフレクションを使うのがよいでしょう。
5-5. Unityのエディタ拡張や内部APIの調査
Unityでは、エディタ拡張やツール開発でリフレクションが使われることがあります。
たとえば、特定の属性が付いたフィールドをエディタ上に表示したり、Unity内部の型情報を調べたりする場面です。
ただし、Unityの内部APIや非公開メンバーにリフレクションでアクセスすると、Unityのバージョンアップで動かなくなる可能性があります。
エディタ拡張でリフレクションを使う場合は、対象のAPIが安定しているか、代替手段がないかを確認することが大切です。
5-6. プラグイン機構や動的読み込み
プラグイン機構では、実行時に外部アセンブリを読み込み、その中の型を調べてインスタンスを生成することがあります。
C#Assembly assembly = Assembly.LoadFrom("Plugin.dll");
foreach (var type in assembly.GetTypes())
{
Console.WriteLine(type.FullName);
}
特定のインターフェースを実装している型だけを探すこともできます。
C#var pluginTypes = assembly.GetTypes()
.Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface);
このように、事前にどのクラスが存在するかわからない場合でも、リフレクションを使えば実行時に型を探して利用できます。
5-7. 共通処理・汎用ツールの作成
リフレクションは、共通処理や汎用ツールの作成にも役立ちます。
たとえば、任意のオブジェクトのプロパティを一覧表示するログ出力処理を作れます。
C#public static void DumpObject(object obj)
{
Type type = obj.GetType();
foreach (var property in type.GetProperties())
{
object? value = property.GetValue(obj);
Console.WriteLine($"{property.Name}: {value}");
}
}
このメソッドは、UserでもProductでも使えます。
C#DumpObject(new User { Id = 1, Name = "Taro" });
型ごとに個別処理を書かなくてよいため、汎用的な仕組みを作るときに便利です。
6. C#リフレクションを使うときの注意点
6-1. 通常のコードよりパフォーマンスが低下しやすい
リフレクションは、通常のメソッド呼び出しやプロパティアクセスより遅くなりやすいです。
C#user.Name
のような直接アクセスに比べて、
C#property.GetValue(user)
は、型情報の探索や動的呼び出しの処理が入るためコストが高くなります。
少数回の呼び出しであれば問題にならないことも多いですが、大量データ処理やループ内で繰り返し使う場合は注意が必要です。
6-2. コンパイル時にエラーを検出しにくい
通常のC#コードでは、存在しないプロパティを呼び出すとコンパイルエラーになります。
C#user.UnknownProperty = "test";
しかし、リフレクションでは文字列で指定するため、コンパイル時にはチェックされません。
C#var property = typeof(User).GetProperty("UnknownProperty");
この場合、実行時にnullが返ります。
リフレクションを使うと、エラーの発見が遅くなりやすい点に注意しましょう。
6-3. 文字列指定によるタイプミスで実行時エラーが起きる
リフレクションでは、プロパティ名やメソッド名を文字列で指定することが多くなります。
C#var property = typeof(User).GetProperty("Nmae");
本当はNameと書くべきところをNmaeと間違えても、コンパイルエラーにはなりません。
対策として、可能な場合はnameofを使いましょう。
C#var property = typeof(User).GetProperty(nameof(User.Name));
nameofを使うと、プロパティ名を変更したときにリファクタリングが効きやすくなります。
6-4. privateメンバーへのアクセスは設計を壊す可能性がある
リフレクションを使えばprivateメンバーにアクセスできます。
C#var field = typeof(User).GetField(
"_password",
BindingFlags.Instance | BindingFlags.NonPublic
);
しかし、privateメンバーは本来、クラスの外部から使われることを想定していない実装詳細です。
privateメンバーに依存すると、クラス内部の実装を少し変えただけで外部コードが壊れる可能性があります。
設計上必要な値や処理であれば、publicメソッドやインターフェースとして公開することを検討しましょう。
6-5. セキュリティリスクを理解して使う必要がある
リフレクションは強力な機能ですが、使い方によってはセキュリティリスクにつながります。
たとえば、外部入力から受け取った型名やメソッド名をそのまま使って実行すると、意図しない型やメソッドを呼び出してしまう可能性があります。
C#Type? type = Type.GetType(inputTypeName);
MethodInfo? method = type?.GetMethod(inputMethodName);
method?.Invoke(instance, null);
このようなコードを書く場合は、許可された型やメソッドだけを実行するホワイトリスト方式にするなど、安全性を確保する必要があります。
6-6. 可読性・保守性が下がりやすい
リフレクションを使うコードは、通常のC#コードに比べて読みづらくなりやすいです。
C#typeof(User)
.GetProperty("Name")
?.SetValue(user, "Taro");
このコードは、単純に次のように書けるなら、そのほうが明確です。
C#user.Name = "Taro";
リフレクションは便利ですが、使いすぎると「どのメソッドが呼ばれるのか」「どのプロパティが使われるのか」が追いにくくなります。
通常のコードで書ける場合は、無理にリフレクションを使わないことが大切です。
6-7. AOT環境やトリミング環境では動作に注意が必要
近年の.NETでは、AOTコンパイルやトリミングを使う場面があります。
トリミングでは、使われていないと判断された型やメンバーがビルド時に削除されることがあります。通常のコードから参照されていないメンバーをリフレクションで呼び出そうとすると、実行時に見つからない可能性があります。
たとえば、文字列で指定したプロパティやメソッドは、ビルドツールから使用箇所を判断しにくい場合があります。
AOT環境やトリミング環境でリフレクションを使う場合は、必要な型やメンバーが削除されないように設計する必要があります。
7. C#リフレクションのパフォーマンス対策
7-1. ループ内で何度もリフレクションを呼ばない
リフレクションでよくあるパフォーマンス問題は、ループ内で毎回GetPropertyやGetMethodを呼び出すことです。
C#foreach (var user in users)
{
var property = typeof(User).GetProperty("Name");
var value = property?.GetValue(user);
}
このコードでは、毎回Nameプロパティを探しています。
プロパティ情報は同じなので、ループの外で取得しましょう。
C#var property = typeof(User).GetProperty("Name");
foreach (var user in users)
{
var value = property?.GetValue(user);
}
これだけでも無駄な処理を減らせます。
7-2. Type・PropertyInfo・MethodInfoをキャッシュする
Type、PropertyInfo、MethodInfoなどは、一度取得したらキャッシュして再利用するのが効果的です。
C#private static readonly PropertyInfo? NameProperty =
typeof(User).GetProperty(nameof(User.Name));
辞書で型ごとにキャッシュする方法もあります。
C#private static readonly Dictionary<Type, PropertyInfo[]> PropertyCache = new();
public static PropertyInfo[] GetCachedProperties(Type type)
{
if (!PropertyCache.TryGetValue(type, out var properties))
{
properties = type.GetProperties();
PropertyCache[type] = properties;
}
return properties;
}
大量データを扱うシリアライザーやマッパーでは、このようなキャッシュが重要になります。
7-3. Delegate化してInvokeのコストを減らす
MethodInfo.Invokeは便利ですが、何度も呼び出すとコストが高くなります。
同じメソッドを繰り返し呼ぶ場合は、デリゲート化を検討できます。
C#public class Calculator
{
public int Add(int x, int y) => x + y;
}
C#var calculator = new Calculator();
MethodInfo method = typeof(Calculator).GetMethod(nameof(Calculator.Add))!;
var del = (Func<Calculator, int, int, int>)
method.CreateDelegate(typeof(Func<Calculator, int, int, int>));
int result = del(calculator, 10, 20);
Console.WriteLine(result);
デリゲート化すると、Invokeより通常のメソッド呼び出しに近い形で実行できます。
ただし、コードは複雑になるため、本当にパフォーマンスが必要な箇所で使いましょう。
7-4. Expression Treeやソースジェネレーターの活用を検討する
リフレクションの代替として、Expression Treeやソースジェネレーターを使う方法もあります。
Expression Treeを使うと、プロパティアクセス用の関数を動的に作成できます。初回の構築にはコストがかかりますが、作成した関数をキャッシュすれば高速に実行できます。
ソースジェネレーターは、コンパイル時にコードを自動生成する仕組みです。リフレクションのように実行時に型情報を調べるのではなく、事前に必要なコードを生成できます。
実行時コストを減らしたい場合や、AOT・トリミング環境で安全に動かしたい場合は、ソースジェネレーターが有力な選択肢になります。
7-5. 使う場所を初期化処理や低頻度処理に限定する
リフレクションは、頻繁に実行される処理よりも、初期化処理や低頻度処理に向いています。
たとえば、アプリケーション起動時に型情報を読み取ってキャッシュしておき、実際の処理ではキャッシュ済みの情報を使う方法です。
C#public static class ReflectionCache
{
public static readonly PropertyInfo[] UserProperties =
typeof(User).GetProperties();
}
リフレクションの使用箇所を限定すると、パフォーマンス問題や保守性の低下を抑えやすくなります。
8. C#リフレクションを使うべき場合・避けるべき場合
8-1. リフレクションを使うべきケース
リフレクションを使うべきなのは、通常のC#コードでは実現しにくい動的な処理が必要な場合です。
たとえば、次のようなケースです。
| ケース | 例 |
|---|---|
| 型が実行時まで決まらない | プラグイン読み込み |
| 汎用的な処理を書きたい | オブジェクトのプロパティ一覧出力 |
| 属性情報を使いたい | バリデーション、表示名、マッピング |
| フレームワーク的な仕組みを作る | DI、ORM、シリアライザー |
| 外部設定で動作を変えたい | 設定ファイルに書かれた型を生成 |
「どの型を扱うか」「どのメンバーを使うか」が実行時に決まる場合、リフレクションは強力な選択肢になります。
8-2. リフレクションを使わないほうがよいケース
一方で、通常のコードで簡単に書ける場合は、リフレクションを使わないほうがよいです。
C#user.Name = "Taro";
で済む処理を、あえて次のように書く必要はありません。
C#typeof(User).GetProperty("Name")?.SetValue(user, "Taro");
リフレクションを使わないほうがよいケースは次のとおりです。
| ケース | 理由 |
|---|---|
| 型がコンパイル時に決まっている | 通常のコードのほうが安全で速い |
| 高頻度で実行される | パフォーマンス問題が起きやすい |
| 単純なプロパティアクセス | 可読性が下がる |
| privateメンバーに依存する | 設計が壊れやすい |
| 文字列指定が多い | 実行時エラーが増えやすい |
リフレクションは便利ですが、必要な場面に限定して使うことが大切です。
8-3. ジェネリクス・インターフェース・ポリモーフィズムで代替できないか考える
リフレクションを使う前に、ジェネリクス、インターフェース、ポリモーフィズムで代替できないかを考えましょう。
たとえば、複数のクラスに共通処理を実行したい場合、インターフェースを使えるかもしれません。
C#public interface IExecutable
{
void Execute();
}
public class JobA : IExecutable
{
public void Execute()
{
Console.WriteLine("JobA");
}
}
呼び出し側はリフレクションを使わずに実行できます。
C#void Run(IExecutable executable)
{
executable.Execute();
}
型が共通化できる場合は、リフレクションよりインターフェースや継承を使ったほうが安全でわかりやすいです。
8-4. dynamicとの違いを理解して使い分ける
C#にはdynamicという仕組みもあります。
C#dynamic user = new User();
user.Name = "Taro";
dynamicは、コンパイル時の型チェックを遅らせ、実行時にメンバー解決を行います。
一方、リフレクションはType、PropertyInfo、MethodInfoなどを使って型情報そのものを調べたり操作したりします。
簡単に言うと、dynamicは「通常のコードに近い書き方で動的に呼ぶ仕組み」、リフレクションは「型情報を詳しく調べて操作する仕組み」です。
動的呼び出しだけが目的ならdynamicで十分な場合もあります。ただし、プロパティ一覧を取得したり、属性を読んだり、アセンブリ内の型を探索したりする場合はリフレクションが必要です。
8-5. nameofや属性設計で安全性を高める
リフレクションを使う場合でも、安全性を高める工夫はできます。
まず、文字列の直接指定を減らすためにnameofを使いましょう。
C#var property = typeof(User).GetProperty(nameof(User.Name));
次に、属性を使って明示的に対象を示す方法もあります。
C#[AttributeUsage(AttributeTargets.Property)]
public class ExportAttribute : Attribute
{
}
C#public class User
{
[Export]
public string Name { get; set; } = "";
public string Password { get; set; } = "";
}
属性が付いたプロパティだけを対象にすれば、意図しないプロパティを処理してしまうリスクを減らせます。
C#var properties = typeof(User).GetProperties()
.Where(p => p.GetCustomAttribute<ExportAttribute>() != null);
リフレクションは柔軟だからこそ、「何を対象にするか」を明確に設計することが重要です。
9. C#リフレクションでよくあるエラーと解決方法
9-1. Type.GetTypeがnullになる原因と対処法
Type.GetTypeがnullになる原因として多いのは、型名が正しくないことです。
C#Type? type = Type.GetType("User");
自作クラスの場合、名前空間を含める必要があります。
C#Type? type = Type.GetType("MyApp.Models.User");
別アセンブリの型を取得する場合は、アセンブリ名も必要になることがあります。
C#Type? type = Type.GetType("MyApp.Models.User, MyApp");
また、現在読み込まれていないアセンブリの型は取得できません。その場合は、先にアセンブリを読み込む必要があります。
C#Assembly assembly = Assembly.LoadFrom("MyApp.Plugins.dll");
Type? type = assembly.GetType("MyApp.Plugins.SamplePlugin");
Type.GetTypeがnullになる場合は、名前空間、アセンブリ名、読み込み状態を確認しましょう。
9-2. GetMethodやGetPropertyで取得できない原因
GetMethodやGetPropertyで取得できない原因として、名前の間違い、アクセス修飾子、staticとinstanceの指定漏れなどがあります。
C#var property = typeof(User).GetProperty("name");
C#では通常、大文字小文字が区別されるため、Nameをnameと書くと取得できません。
privateメンバーを取得したい場合は、BindingFlags.NonPublicを指定します。
C#var property = typeof(User).GetProperty(
"Secret",
BindingFlags.Instance | BindingFlags.NonPublic
);
staticメンバーの場合は、BindingFlags.Staticが必要です。
C#var method = typeof(User).GetMethod(
"CreateDefault",
BindingFlags.Static | BindingFlags.Public
);
取得できないときは、対象がpublicかprivateか、instanceかstaticか、名前が完全に一致しているかを確認しましょう。
9-3. TargetInvocationExceptionが発生する原因
MethodInfo.Invokeで呼び出したメソッドの内部で例外が発生すると、TargetInvocationExceptionでラップされることがあります。
C#try
{
method.Invoke(instance, null);
}
catch (TargetInvocationException ex)
{
Console.WriteLine(ex.InnerException?.Message);
}
重要なのは、実際の原因はTargetInvocationExceptionそのものではなく、InnerExceptionに入っていることです。
たとえば、呼び出したメソッドの中でInvalidOperationExceptionが発生した場合、InnerExceptionにその例外が入ります。
リフレクション経由でメソッドを呼ぶときは、エラー調査のためにInnerExceptionを確認しましょう。
9-4. AmbiguousMatchExceptionが発生する原因
AmbiguousMatchExceptionは、条件に一致するメソッドやプロパティが複数ある場合に発生します。
よくある原因は、メソッドのオーバーロードです。
C#public class Calculator
{
public int Add(int x, int y) => x + y;
public double Add(double x, double y) => x + y;
}
この状態で次のように書くと、どちらのAddを取得すればよいか判断できない場合があります。
C#var method = typeof(Calculator).GetMethod("Add");
引数の型を指定して取得しましょう。
C#var method = typeof(Calculator).GetMethod(
"Add",
new[] { typeof(int), typeof(int) }
);
オーバーロードされたメソッドを扱う場合は、引数の型まで明示することが大切です。
9-5. privateメンバーにアクセスできない場合の確認ポイント
privateメンバーにアクセスできない場合は、まずBindingFlagsを確認しましょう。
C#var field = typeof(User).GetField(
"_secret",
BindingFlags.Instance | BindingFlags.NonPublic
);
staticのprivateメンバーなら、InstanceではなくStaticを指定します。
C#var field = typeof(User).GetField(
"_secret",
BindingFlags.Static | BindingFlags.NonPublic
);
また、親クラスに定義されたprivateメンバーは、派生クラスのTypeから単純に取得できない場合があります。その場合は、定義元の型を指定して取得します。
C#var field = typeof(BaseUser).GetField(
"_secret",
BindingFlags.Instance | BindingFlags.NonPublic
);
privateメンバーは実装詳細なので、取得できたとしても利用には注意が必要です。
9-6. .NETのバージョンや実行環境による違い
リフレクションの挙動は、.NET Framework、.NET、Unity、AOT環境などで違いが出ることがあります。
特に注意したいのは、次のような環境です。
| 環境 | 注意点 |
|---|---|
| .NET Framework | 古いAPIや権限モデルの影響を受ける場合がある |
| .NET | トリミングやAOTを使う場合に注意が必要 |
| Unity | IL2CPP環境でリフレクション対象が制限される場合がある |
| Blazor WebAssembly | 実行環境の制約を受ける場合がある |
| Native AOT | 動的な型探索が制限される場合がある |
特に、文字列で型やメンバーを指定するコードは、ビルド時の解析で使用箇所を判断しにくくなります。
ライブラリやフレームワークを作る場合は、対象環境でリフレクションが安全に動くか確認しましょう。
10. C#リフレクションに関するよくある質問
10-1. リフレクションは初心者でも覚えるべき?
初心者が最初から深く覚える必要はありません。
まずは、クラス、プロパティ、メソッド、インターフェース、ジェネリクスなどの基本を理解することが大切です。
ただし、C#で実務開発をしていると、DI、ORM、JSONシリアライズ、属性などを通じてリフレクションに触れる機会があります。
そのため、「リフレクションを使うと型情報を実行時に調べられる」という基本概念は早めに知っておくと役立ちます。
10-2. リフレクションは実務で使われる?
リフレクションは実務でも使われます。
特に、フレームワーク、ライブラリ、共通基盤、開発支援ツールなどでよく使われます。
たとえば、次のような場面です。
| 場面 | 用途 |
|---|---|
| DIコンテナ | コンストラクタを調べて依存関係を注入する |
| JSON変換 | プロパティ情報を読み取る |
| ORM | クラスとテーブルを対応付ける |
| テスト | 内部状態を確認する |
| プラグイン | 外部アセンブリから型を読み込む |
| エディタ拡張 | 属性や型情報をもとに表示を変える |
ただし、業務ロジックの中でリフレクションを多用することはあまり推奨されません。必要な箇所に限定して使うのが一般的です。
10-3. リフレクションは遅いから使わないほうがいい?
リフレクションは通常のコードより遅くなりやすいですが、「絶対に使ってはいけない」というわけではありません。
問題は、使う場所と頻度です。
アプリケーション起動時の初期化処理や、少数回しか実行されない処理であれば、大きな問題にならないことも多いです。
一方で、数万回、数百万回と繰り返す処理の中で毎回リフレクションを使うと、パフォーマンスに影響する可能性があります。
頻繁に使う場合は、PropertyInfoやMethodInfoをキャッシュする、デリゲート化する、ソースジェネレーターを使うなどの対策を検討しましょう。
10-4. リフレクションでprivateにアクセスしてもよい?
技術的には、リフレクションでprivateメンバーにアクセスできます。
しかし、基本的には推奨されません。
privateメンバーはクラス内部の実装詳細であり、外部から使われることを想定していません。そこに依存すると、内部実装の変更に弱いコードになります。
どうしても必要な場合は、単体テスト、デバッグ、移行作業、互換性対応など、用途を限定して使いましょう。
設計として必要な機能であれば、publicメソッド、internalメソッド、インターフェースなどで適切に公開するほうが安全です。
10-5. リフレクションと属性はセットで覚えるべき?
リフレクションと属性は相性がよいため、セットで覚えると理解が深まります。
属性は、クラスやプロパティ、メソッドに追加情報を付ける仕組みです。
C#[DisplayName("ユーザー名")]
public string Name { get; set; } = "";
リフレクションを使うと、この属性情報を読み取れます。
C#var attribute = property.GetCustomAttribute<DisplayNameAttribute>();
属性だけでは情報を付けるだけですが、リフレクションと組み合わせることで、その情報をもとに処理を変えられます。
バリデーション、表示名、CSV出力、JSON変換、マッピングなどでよく使われる考え方です。
10-6. リフレクションとソースジェネレーターはどう違う?
リフレクションは、実行時に型情報を調べて処理する仕組みです。
一方、ソースジェネレーターは、コンパイル時にコードを生成する仕組みです。
| 項目 | リフレクション | ソースジェネレーター |
|---|---|---|
| 実行タイミング | 実行時 | コンパイル時 |
| 柔軟性 | 高い | 設計次第 |
| パフォーマンス | 遅くなりやすい | 高速にしやすい |
| 型安全性 | 低くなりやすい | 高くしやすい |
| AOT対応 | 注意が必要 | 対応しやすい |
リフレクションは実行時に柔軟に動かせるのが強みです。一方、ソースジェネレーターは事前にコードを生成するため、高速で安全なコードを作りやすいです。
近年の.NETでは、パフォーマンスやAOT対応を重視する場合、リフレクションの代わりにソースジェネレーターを使う設計も増えています。
まとめ
C#リフレクションは、実行時に型情報を調べたり、プロパティやメソッドを動的に操作したりできる強力な仕組みです。
GetType、typeof、Type.GetTypeで型情報を取得し、GetProperties、GetMethods、GetFieldsなどを使ってメンバー情報を調べます。さらに、GetValue、SetValue、Invoke、Activator.CreateInstanceを使えば、値の取得・変更、メソッド実行、インスタンス生成も可能です。
リフレクションは、JSONシリアライズ、DIコンテナ、ORM、プラグイン機構、属性ベースの処理など、多くの仕組みの内部で使われています。
一方で、通常のC#コードよりパフォーマンスが低下しやすく、コンパイル時にエラーを検出しにくいというデメリットもあります。文字列指定によるタイプミス、privateメンバーへの依存、AOTやトリミング環境での制約にも注意が必要です。
リフレクションを使うときは、次のポイントを意識しましょう。
| ポイント | 内容 |
|---|---|
| 必要な場面に限定する | 通常のコードで書けるなら使わない |
| 文字列指定を減らす | nameofを活用する |
| 取得結果を確認する | nullチェックを行う |
| パフォーマンス対策をする | PropertyInfoやMethodInfoをキャッシュする |
| privateアクセスは慎重に使う | 設計を壊さないようにする |
| 実行環境に注意する | AOTやトリミング環境では特に確認する |
C#リフレクションは、初心者が最初に多用する機能ではありません。しかし、仕組みを理解しておくと、フレームワークの内部動作や汎用的なライブラリ設計への理解が深まります。
まずは、typeof、GetProperties、GetProperty、GetValue、SetValue、Invokeといった基本から試してみると、C#リフレクションの考え方をつかみやすくなります。

