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では、ToStringEqualsなど、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意味
Publicpublicメンバーを対象にする
NonPublicprivateやprotectedなど非公開メンバーを対象にする
Instanceインスタンスメンバーを対象にする
Staticstaticメンバーを対象にする
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);

Namestring型なので、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クラスのIdNameをデータベースのカラムに対応付けます。

リフレクションを使うことで、クラスのプロパティを読み取り、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. ループ内で何度もリフレクションを呼ばない

リフレクションでよくあるパフォーマンス問題は、ループ内で毎回GetPropertyGetMethodを呼び出すことです。

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をキャッシュする

TypePropertyInfoMethodInfoなどは、一度取得したらキャッシュして再利用するのが効果的です。

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は、コンパイル時の型チェックを遅らせ、実行時にメンバー解決を行います。

一方、リフレクションはTypePropertyInfoMethodInfoなどを使って型情報そのものを調べたり操作したりします。

簡単に言うと、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.GetTypenullになる原因として多いのは、型名が正しくないことです。

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.GetTypenullになる場合は、名前空間、アセンブリ名、読み込み状態を確認しましょう。

9-2. GetMethodやGetPropertyで取得できない原因

GetMethodGetPropertyで取得できない原因として、名前の間違い、アクセス修飾子、staticとinstanceの指定漏れなどがあります。

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

C#では通常、大文字小文字が区別されるため、Namenameと書くと取得できません。

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を使う場合に注意が必要
UnityIL2CPP環境でリフレクション対象が制限される場合がある
Blazor WebAssembly実行環境の制約を受ける場合がある
Native AOT動的な型探索が制限される場合がある

特に、文字列で型やメンバーを指定するコードは、ビルド時の解析で使用箇所を判断しにくくなります。

ライブラリやフレームワークを作る場合は、対象環境でリフレクションが安全に動くか確認しましょう。

10. C#リフレクションに関するよくある質問

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

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

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

ただし、C#で実務開発をしていると、DI、ORM、JSONシリアライズ、属性などを通じてリフレクションに触れる機会があります。

そのため、「リフレクションを使うと型情報を実行時に調べられる」という基本概念は早めに知っておくと役立ちます。

10-2. リフレクションは実務で使われる?

リフレクションは実務でも使われます。

特に、フレームワーク、ライブラリ、共通基盤、開発支援ツールなどでよく使われます。

たとえば、次のような場面です。

場面用途
DIコンテナコンストラクタを調べて依存関係を注入する
JSON変換プロパティ情報を読み取る
ORMクラスとテーブルを対応付ける
テスト内部状態を確認する
プラグイン外部アセンブリから型を読み込む
エディタ拡張属性や型情報をもとに表示を変える

ただし、業務ロジックの中でリフレクションを多用することはあまり推奨されません。必要な箇所に限定して使うのが一般的です。

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

リフレクションは通常のコードより遅くなりやすいですが、「絶対に使ってはいけない」というわけではありません。

問題は、使う場所と頻度です。

アプリケーション起動時の初期化処理や、少数回しか実行されない処理であれば、大きな問題にならないことも多いです。

一方で、数万回、数百万回と繰り返す処理の中で毎回リフレクションを使うと、パフォーマンスに影響する可能性があります。

頻繁に使う場合は、PropertyInfoMethodInfoをキャッシュする、デリゲート化する、ソースジェネレーターを使うなどの対策を検討しましょう。

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#リフレクションは、実行時に型情報を調べたり、プロパティやメソッドを動的に操作したりできる強力な仕組みです。

GetTypetypeofType.GetTypeで型情報を取得し、GetPropertiesGetMethodsGetFieldsなどを使ってメンバー情報を調べます。さらに、GetValueSetValueInvokeActivator.CreateInstanceを使えば、値の取得・変更、メソッド実行、インスタンス生成も可能です。

リフレクションは、JSONシリアライズ、DIコンテナ、ORM、プラグイン機構、属性ベースの処理など、多くの仕組みの内部で使われています。

一方で、通常のC#コードよりパフォーマンスが低下しやすく、コンパイル時にエラーを検出しにくいというデメリットもあります。文字列指定によるタイプミス、privateメンバーへの依存、AOTやトリミング環境での制約にも注意が必要です。

リフレクションを使うときは、次のポイントを意識しましょう。

ポイント内容
必要な場面に限定する通常のコードで書けるなら使わない
文字列指定を減らすnameofを活用する
取得結果を確認するnullチェックを行う
パフォーマンス対策をするPropertyInfoMethodInfoをキャッシュする
privateアクセスは慎重に使う設計を壊さないようにする
実行環境に注意するAOTやトリミング環境では特に確認する

C#リフレクションは、初心者が最初に多用する機能ではありません。しかし、仕組みを理解しておくと、フレームワークの内部動作や汎用的なライブラリ設計への理解が深まります。

まずは、typeofGetPropertiesGetPropertyGetValueSetValueInvokeといった基本から試してみると、C#リフレクションの考え方をつかみやすくなります。