C#リフレクションとは?基本の使い方からプロパティ・メソッド取得までわかりやすく解説

はじめに

C#のリフレクションは、プログラムの実行中にクラスやプロパティ、メソッドなどの型情報を取得し、動的に操作するための仕組みです。

通常のC#コードでは、利用する型やメンバーをコンパイル時に決定します。一方、リフレクションを使うと、実行時に型を調べてプロパティの値を取得したり、名前を指定してメソッドを実行したりできます。

リフレクションは、JSONシリアライザー、DIコンテナ、ORM、テストフレームワーク、プラグイン機能などで広く使われています。ただし、通常のコードより処理が複雑になりやすく、パフォーマンスや型安全性にも注意が必要です。

この記事では、C#リフレクションの基本から、プロパティ・メソッド・フィールド・コンストラクタ・属性の取得方法、実践的なサンプル、よくあるエラーまでわかりやすく解説します。

1. C#リフレクションとは?

1-1. リフレクションの意味と役割

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

C#では、主にSystem.Reflection名前空間のクラスを使用します。型そのものの情報はTypeクラスで表され、プロパティはPropertyInfo、メソッドはMethodInfo、フィールドはFieldInfoとして取得できます。

C#
using System;
using System.Reflection;

Type type = typeof(string);

Console.WriteLine(type.Name);
Console.WriteLine(type.FullName);

実行結果の例は次のとおりです。

String
System.String

このように、リフレクションを利用すると、型名や名前空間、定義されているメンバーなどをプログラムから確認できます。

1-2. 通常のコード実行との違い

通常のC#コードでは、アクセスするプロパティや呼び出すメソッドをソースコード上で直接指定します。

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

Console.WriteLine(user.Name);

一方、リフレクションでは、文字列でプロパティ名を指定して値を取得できます。

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

PropertyInfo? property = user.GetType().GetProperty("Name");
object? value = property?.GetValue(user);

Console.WriteLine(value);

通常のコードはコンパイル時にメンバーの存在が確認されますが、リフレクションでは実行時に検索します。そのため柔軟性が高い反面、プロパティ名のスペルミスなどがコンパイルエラーにならず、実行時の問題として現れます。

1-3. リフレクションでできること

C#のリフレクションでは、主に次のような処理ができます。

  • 型名、名前空間、基底クラス、実装インターフェースを取得する

  • プロパティやフィールドの一覧を取得する

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

  • メソッドの一覧や引数、戻り値を調べる

  • メソッド名を指定して実行する

  • コンストラクタを取得してインスタンスを生成する

  • クラスやメンバーに付与された属性を読み取る

  • アセンブリに含まれる型を検索する

  • privateメンバーを取得する

実行時まで対象の型が決まらない処理や、多数の型を共通ルールで扱う処理で特に役立ちます。

1-4. C#でリフレクションが使われる主な場面

リフレクションは、次のようなライブラリやフレームワークの内部でよく使われます。

JSON変換

オブジェクトのプロパティを調べ、プロパティ名と値をJSONに変換します。

DIコンテナ

コンストラクタや引数の型を調べ、必要な依存オブジェクトを自動的に生成して渡します。

ORM

クラスのプロパティとデータベースのカラムを対応付け、取得したデータをオブジェクトに設定します。

テストフレームワーク

テスト用の属性が付いたクラスやメソッドを探し、対象のテストメソッドを実行します。

プラグイン機能

外部アセンブリを読み込み、特定のインターフェースを実装した型を検索して実行します。

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

2-1. Typeクラスとは

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

Typeオブジェクトを取得すると、型に関する次のような情報を確認できます。

C#
using System;

Type type = typeof(User);

Console.WriteLine($"型名: {type.Name}");
Console.WriteLine($"完全修飾名: {type.FullName}");
Console.WriteLine($"名前空間: {type.Namespace}");
Console.WriteLine($"クラスか: {type.IsClass}");
Console.WriteLine($"値型か: {type.IsValueType}");
Console.WriteLine($"抽象型か: {type.IsAbstract}");

Typeには、プロパティやメソッドを取得するためのGetPropertiesGetMethodsGetFieldsなどのメソッドも用意されています。

2-2. typeofで型情報を取得する方法

typeof演算子を使うと、型名からTypeオブジェクトを取得できます。

C#
Type stringType = typeof(string);
Type userType = typeof(User);
Type listType = typeof(List<int>);

typeofは、対象の型がコンパイル時に決まっている場合に使用します。

C#
Type type = typeof(User);

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

ジェネリック型の定義そのものを取得することも可能です。

C#
Type genericType = typeof(List<>);

Console.WriteLine(genericType.IsGenericTypeDefinition);

2-3. GetTypeでインスタンスから型情報を取得する方法

GetTypeメソッドを使うと、インスタンスの実際の型を取得できます。

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

Console.WriteLine(type.Name);

typeofGetTypeの大きな違いは、GetTypeが実行時の型を返す点です。

C#
Animal animal = new Dog();

Console.WriteLine(typeof(Animal).Name);
Console.WriteLine(animal.GetType().Name);

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

Animal
Dog

変数の宣言型ではなく、実際に代入されているオブジェクトの型を調べたい場合はGetTypeを使います。

なお、nullに対してGetTypeを呼び出すとNullReferenceExceptionが発生するため注意が必要です。

2-4. Assemblyから型情報を取得する方法

Assemblyクラスを使うと、アセンブリに含まれる型を取得できます。

C#
using System.Reflection;

Assembly assembly = Assembly.GetExecutingAssembly();

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

外部DLLを読み込む場合は、Assembly.LoadFromなどを利用できます。

C#
Assembly assembly = Assembly.LoadFrom("SamplePlugin.dll");

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

完全修飾名を指定して、特定の型を取得することも可能です。

C#
Type? type = assembly.GetType("SamplePlugin.SampleService");

if (type is not null)
{
Console.WriteLine(type.Name);
}

外部ファイルの読み込みでは、ファイルが存在しない場合や依存アセンブリを解決できない場合に例外が発生する可能性があります。

2-5. BindingFlagsの基本

BindingFlagsは、リフレクションで検索するメンバーの範囲を指定する列挙型です。

主な値は次のとおりです。

意味
BindingFlags.Publicpublicメンバーを検索する
BindingFlags.NonPublicpublic以外のメンバーを検索する
BindingFlags.Instanceインスタンスメンバーを検索する
BindingFlags.Staticstaticメンバーを検索する
BindingFlags.DeclaredOnly対象の型で直接定義されたメンバーだけを検索する
BindingFlags.IgnoreCase大文字と小文字を区別せず検索する
BindingFlags.FlattenHierarchy継承されたpublic・protectedのstaticメンバーを検索対象に含める

複数の値は|演算子で組み合わせます。

C#
BindingFlags flags =
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance;

MethodInfo[] methods = typeof(User).GetMethods(flags);

BindingFlagsを明示的に指定する場合、通常はアクセス範囲と対象種別の両方を指定します。

C#
PropertyInfo? property = typeof(User).GetProperty(
"SecretCode",
BindingFlags.NonPublic | BindingFlags.Instance);

BindingFlags.NonPublicだけを指定し、BindingFlags.InstanceまたはBindingFlags.Staticを指定し忘れると、期待したメンバーを取得できません。

3. リフレクションでプロパティを取得する方法

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

GetPropertiesメソッドを使うと、型が持つプロパティの一覧を取得できます。

C#
public class User
{
public int Id { get; set; }

public string Name { get; set; } = string.Empty;

public DateTime CreatedAt { get; set; }
}
C#
Type type = typeof(User);
PropertyInfo[] properties = type.GetProperties();

foreach (PropertyInfo property in properties)
{
Console.WriteLine(property.Name);
}

実行結果は次のとおりです。

Id
Name
CreatedAt

引数なしのGetPropertiesは、基本的にpublicなインスタンスプロパティとstaticプロパティを取得します。

3-2. GetPropertyで特定のプロパティを取得する

特定のプロパティだけを取得する場合はGetPropertyを使用します。

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

if (property is not null)
{
Console.WriteLine(property.Name);
}

一致するプロパティが存在しない場合、GetPropertynullを返します。そのため、取得後のnullチェックが必要です。

C#
PropertyInfo property = typeof(User).GetProperty("Name")
?? throw new InvalidOperationException("Nameプロパティが見つかりません。");

標準では大文字と小文字が区別されます。区別せずに検索する場合は、BindingFlags.IgnoreCaseを指定します。

C#
PropertyInfo? property = typeof(User).GetProperty(
"name",
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.IgnoreCase);

3-3. プロパティ名・型・属性を確認する

PropertyInfoから、プロパティ名やデータ型、読み書きの可否などを確認できます。

C#
PropertyInfo[] properties = typeof(User).GetProperties();

foreach (PropertyInfo property in properties)
{
Console.WriteLine($"名前: {property.Name}");
Console.WriteLine($"型: {property.PropertyType.Name}");
Console.WriteLine($"読み取り可能: {property.CanRead}");
Console.WriteLine($"書き込み可能: {property.CanWrite}");
Console.WriteLine($"宣言元: {property.DeclaringType?.Name}");
}

getterとsetterのアクセス範囲を確認することもできます。

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

MethodInfo? getter = property?.GetGetMethod(nonPublic: true);
MethodInfo? setter = property?.GetSetMethod(nonPublic: true);

Console.WriteLine($"getter: {getter?.Attributes}");
Console.WriteLine($"setter: {setter?.Attributes}");

3-4. GetValueでプロパティの値を取得する

PropertyInfo.GetValueを使うと、指定したオブジェクトからプロパティの値を取得できます。

C#
var user = new User
{
Id = 1,
Name = "山田",
CreatedAt = DateTime.Today
};

PropertyInfo? property = user.GetType().GetProperty("Name");
object? value = property?.GetValue(user);

Console.WriteLine(value);

戻り値の型はobject?です。特定の型として扱う場合はキャストします。

C#
string? name = property?.GetValue(user) as string;

値型の場合は、パターンマッチングを使うと安全です。

C#
PropertyInfo? idProperty = user.GetType().GetProperty("Id");

if (idProperty?.GetValue(user) is int id)
{
Console.WriteLine(id);
}

staticプロパティの値を取得する場合、対象オブジェクトにはnullを指定します。

C#
object? value = staticProperty.GetValue(null);

3-5. SetValueでプロパティの値を変更する

PropertyInfo.SetValueを使うと、プロパティの値を動的に変更できます。

C#
var user = new User
{
Name = "変更前"
};

PropertyInfo? property = user.GetType().GetProperty("Name");

if (property is not null && property.CanWrite)
{
property.SetValue(user, "変更後");
}

Console.WriteLine(user.Name);

設定する値は、プロパティの型と互換性がなければなりません。

C#
PropertyInfo? idProperty = typeof(User).GetProperty("Id");
idProperty?.SetValue(user, 100);

文字列を数値型のプロパティに設定する場合は、事前に変換が必要です。

C#
string input = "100";
object convertedValue = Convert.ChangeType(input, typeof(int));

idProperty?.SetValue(user, convertedValue);

initアクセサー、読み取り専用プロパティ、インデクサーなどは、通常の書き込み可能プロパティと同じように扱えない場合があります。

3-6. privateプロパティを取得する方法

privateプロパティを取得するには、BindingFlags.NonPublicBindingFlags.Instanceを指定します。

C#
public class Account
{
private string SecretCode { get; set; } = "ABC123";
}
C#
var account = new Account();

PropertyInfo? property = typeof(Account).GetProperty(
"SecretCode",
BindingFlags.NonPublic | BindingFlags.Instance);

object? value = property?.GetValue(account);

Console.WriteLine(value);

値を変更することもできます。

C#
property?.SetValue(account, "XYZ999");

ただし、privateメンバーはクラスの内部実装です。リフレクションで外部から操作すると、設計上の前提やカプセル化を壊す可能性があります。

4. リフレクションでメソッドを取得・実行する方法

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

GetMethodsメソッドを使うと、型が持つメソッドの一覧を取得できます。

C#
public class Calculator
{
public int Add(int x, int y)
{
return x + y;
}

public int Subtract(int x, int y)
{
return x - y;
}
}
C#
MethodInfo[] methods = typeof(Calculator).GetMethods();

foreach (MethodInfo method in methods)
{
Console.WriteLine(method.Name);
}

この方法では、ToStringGetTypeなど、基底クラスから継承したpublicメソッドも取得されます。

対象クラスで直接定義されたメソッドだけを取得する場合は、BindingFlags.DeclaredOnlyを指定します。

C#
MethodInfo[] methods = typeof(Calculator).GetMethods(
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.DeclaredOnly);

4-2. GetMethodで特定のメソッドを取得する

GetMethodを使うと、名前を指定してメソッドを取得できます。

C#
MethodInfo? method = typeof(Calculator).GetMethod("Add");

if (method is not null)
{
Console.WriteLine(method.Name);
}

同じ名前のオーバーロードが複数存在する場合は、引数の型も指定します。

C#
public class Formatter
{
public string Format(int value)
{
return value.ToString();
}

public string Format(DateTime value)
{
return value.ToString("yyyy-MM-dd");
}
}
C#
MethodInfo? method = typeof(Formatter).GetMethod(
"Format",
new[] { typeof(int) });

引数の型を指定しないまま複数の候補が一致すると、AmbiguousMatchExceptionが発生することがあります。

4-3. メソッド名・戻り値・引数を確認する

MethodInfoから、メソッド名、戻り値の型、引数などを確認できます。

C#
MethodInfo? method = typeof(Calculator).GetMethod("Add");

if (method is not null)
{
Console.WriteLine($"メソッド名: {method.Name}");
Console.WriteLine($"戻り値: {method.ReturnType.Name}");
Console.WriteLine($"staticか: {method.IsStatic}");
Console.WriteLine($"publicか: {method.IsPublic}");

foreach (ParameterInfo parameter in method.GetParameters())
{
Console.WriteLine(
$"引数名: {parameter.Name}, 型: {parameter.ParameterType.Name}");
}
}

省略可能な引数かどうかは、ParameterInfo.IsOptionalで確認できます。

C#
foreach (ParameterInfo parameter in method.GetParameters())
{
Console.WriteLine($"省略可能: {parameter.IsOptional}");
Console.WriteLine($"既定値: {parameter.DefaultValue}");
}

4-4. Invokeでメソッドを実行する

取得したメソッドは、MethodInfo.Invokeで実行できます。

C#
public class Greeter
{
public string GetMessage()
{
return "こんにちは";
}
}
C#
var greeter = new Greeter();
MethodInfo? method = typeof(Greeter).GetMethod("GetMessage");

object? result = method?.Invoke(greeter, null);

Console.WriteLine(result);

第1引数にはメソッドを実行するインスタンス、第2引数にはメソッドへ渡す引数の配列を指定します。

引数がない場合、第2引数にはnullまたは空配列を指定できます。

C#
object? result = method?.Invoke(greeter, Array.Empty<object>());

staticメソッドを実行する場合、第1引数にはnullを指定します。

C#
MethodInfo? method = typeof(Math).GetMethod(
"Abs",
new[] { typeof(int) });

object? result = method?.Invoke(null, new object[] { -10 });

Console.WriteLine(result);

4-5. 引数ありメソッドを実行する方法

引数があるメソッドを実行する場合は、引数をobject?[]として渡します。

C#
var calculator = new Calculator();

MethodInfo? method = typeof(Calculator).GetMethod(
"Add",
new[] { typeof(int), typeof(int) });

object? result = method?.Invoke(
calculator,
new object?[] { 10, 20 });

Console.WriteLine(result);

戻り値を特定の型として取得する場合は、キャストまたはパターンマッチングを使用します。

C#
if (result is int total)
{
Console.WriteLine($"計算結果: {total}");
}

引数の数や型がメソッド定義と一致しない場合、例外が発生します。

4-6. privateメソッドを呼び出す方法

privateメソッドを取得するには、BindingFlags.NonPublicを使用します。

C#
public class TokenService
{
private string GenerateToken(int userId)
{
return $"TOKEN-{userId}";
}
}
C#
var service = new TokenService();

MethodInfo? method = typeof(TokenService).GetMethod(
"GenerateToken",
BindingFlags.NonPublic | BindingFlags.Instance);

object? result = method?.Invoke(
service,
new object?[] { 10 });

Console.WriteLine(result);

privateメソッドの呼び出しは技術的には可能ですが、通常のアプリケーションコードで多用するべきではありません。テストやフレームワーク内部など、用途を限定して利用するのが適切です。

5. リフレクションでフィールド・コンストラクタ・属性を扱う方法

5-1. GetFieldsでフィールドを取得する

フィールドの一覧はGetFieldsで取得できます。

C#
public class Product
{
public static int Count;

public int Id;

private decimal _price;
}

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

C#
FieldInfo[] fields = typeof(Product).GetFields();

foreach (FieldInfo field in fields)
{
Console.WriteLine($"{field.Name}: {field.FieldType.Name}");
}

privateフィールドも含める場合は、BindingFlagsを指定します。

C#
FieldInfo[] fields = typeof(Product).GetFields(
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.Static);

値の取得と変更には、GetValueSetValueを使います。

C#
var product = new Product();

FieldInfo? field = typeof(Product).GetField(
"_price",
BindingFlags.NonPublic | BindingFlags.Instance);

field?.SetValue(product, 1_500m);

object? value = field?.GetValue(product);
Console.WriteLine(value);

5-2. GetConstructorsでコンストラクタを取得する

GetConstructorsを使うと、型に定義されているコンストラクタを取得できます。

C#
public class Customer
{
public Customer()
{
}

public Customer(int id, string name)
{
Id = id;
Name = name;
}

public int Id { get; }

public string Name { get; } = string.Empty;
}
C#
ConstructorInfo[] constructors = typeof(Customer).GetConstructors();

foreach (ConstructorInfo constructor in constructors)
{
ParameterInfo[] parameters = constructor.GetParameters();

Console.WriteLine($"引数の数: {parameters.Length}");

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

特定の引数を持つコンストラクタを取得することもできます。

C#
ConstructorInfo? constructor = typeof(Customer).GetConstructor(
new[] { typeof(int), typeof(string) });

取得したコンストラクタはInvokeで実行できます。

C#
object? instance = constructor?.Invoke(
new object?[] { 1, "田中" });

5-3. Activator.CreateInstanceでインスタンスを生成する

Activator.CreateInstanceを使うと、Typeから動的にインスタンスを生成できます。

C#
Type type = typeof(Customer);

object? instance = Activator.CreateInstance(type);

ジェネリック版を使うこともできます。

C#
Customer? customer = Activator.CreateInstance<Customer>();

引数付きコンストラクタを呼び出す場合は、引数を指定します。

C#
object? instance = Activator.CreateInstance(
typeof(Customer),
new object?[] { 1, "田中" });

生成後に型を確認して利用します。

C#
if (instance is Customer customer)
{
Console.WriteLine(customer.Name);
}

該当するコンストラクタが存在しない場合は、MissingMethodExceptionなどが発生する可能性があります。

5-4. GetCustomAttributesで属性を取得する

属性とは、クラスやプロパティ、メソッドなどに追加情報を付与する仕組みです。

C#
[Obsolete("OldMethodは非推奨です。")]
public void OldMethod()
{
}

GetCustomAttributesを使うと、付与された属性を取得できます。

C#
MethodInfo? method = typeof(SampleService).GetMethod("OldMethod");

IEnumerable<Attribute> attributes =
method?.GetCustomAttributes() ?? Enumerable.Empty<Attribute>();

foreach (Attribute attribute in attributes)
{
Console.WriteLine(attribute.GetType().Name);
}

特定の属性だけを取得する場合は、ジェネリック版が便利です。

C#
ObsoleteAttribute? attribute =
method?.GetCustomAttribute<ObsoleteAttribute>();

if (attribute is not null)
{
Console.WriteLine(attribute.Message);
}

属性が付いているかだけを調べる場合は、IsDefinedも利用できます。

C#
bool hasAttribute = method?.IsDefined(
typeof(ObsoleteAttribute),
inherit: false) ?? false;

5-5. 独自属性をリフレクションで読み取る方法

独自属性は、Attributeクラスを継承して定義します。

C#
[AttributeUsage(AttributeTargets.Class)]
public sealed class DisplayNameAttribute : Attribute
{
public DisplayNameAttribute(string name)
{
Name = name;
}

public string Name { get; }
}

クラスに属性を付与します。

C#
[DisplayName("ユーザー情報")]
public class User
{
public int Id { get; set; }

public string Name { get; set; } = string.Empty;
}

リフレクションで属性を読み取ります。

C#
DisplayNameAttribute? attribute =
typeof(User).GetCustomAttribute<DisplayNameAttribute>();

if (attribute is not null)
{
Console.WriteLine(attribute.Name);
}

プロパティ用の独自属性も作成できます。

C#
[AttributeUsage(AttributeTargets.Property)]
public sealed class ColumnNameAttribute : Attribute
{
public ColumnNameAttribute(string name)
{
Name = name;
}

public string Name { get; }
}
C#
public class Product
{
[ColumnName("product_name")]
public string Name { get; set; } = string.Empty;
}
C#
PropertyInfo? property = typeof(Product).GetProperty("Name");

ColumnNameAttribute? attribute =
property?.GetCustomAttribute<ColumnNameAttribute>();

Console.WriteLine(attribute?.Name);

6. C#リフレクションの実践サンプル

6-1. クラスのプロパティ一覧を表示するサンプル

次のメソッドは、指定した型のpublicプロパティを一覧表示します。

C#
using System.Reflection;

static void PrintProperties<T>()
{
Type type = typeof(T);

Console.WriteLine($"型: {type.FullName}");

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

呼び出し例です。

C#
PrintProperties<User>();

Typeを引数として受け取る形式にすると、実行時に決まる型にも対応できます。

C#
static void PrintProperties(Type type)
{
foreach (PropertyInfo property in type.GetProperties())
{
Console.WriteLine(
$"{property.Name} : {property.PropertyType.Name}");
}
}

6-2. オブジェクトの値を動的に取得するサンプル

オブジェクトが持つすべてのpublicプロパティについて、名前と値を取得する例です。

C#
static Dictionary<string, object?> ToDictionary(object target)
{
ArgumentNullException.ThrowIfNull(target);

return target
.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(property =>
property.CanRead &&
property.GetIndexParameters().Length == 0)
.ToDictionary(
property => property.Name,
property => property.GetValue(target));
}

使用例です。

C#
var user = new User
{
Id = 1,
Name = "鈴木",
CreatedAt = new DateTime(2026, 1, 10)
};

Dictionary<string, object?> values = ToDictionary(user);

foreach ((string name, object? value) in values)
{
Console.WriteLine($"{name}: {value}");
}

インデクサーには引数が必要なため、GetIndexParameters().Length == 0で除外しています。

6-3. メソッド名を指定して実行するサンプル

文字列で指定したメソッドを実行する汎用メソッドの例です。

C#
static object? ExecuteMethod(
object target,
string methodName,
params object?[] arguments)
{
ArgumentNullException.ThrowIfNull(target);

Type[] argumentTypes = arguments
.Select(argument => argument?.GetType() ?? typeof(object))
.ToArray();

MethodInfo? method = target
.GetType()
.GetMethod(methodName, argumentTypes);

if (method is null)
{
throw new MissingMethodException(
target.GetType().FullName,
methodName);
}

return method.Invoke(target, arguments);
}

使用例です。

C#
var calculator = new Calculator();

object? result = ExecuteMethod(
calculator,
"Add",
10,
20);

Console.WriteLine(result);

引数にnullが含まれる場合や、基底型・インターフェース型で定義された引数を扱う場合は、実引数の型だけで完全に一致するメソッドを特定できないことがあります。実用的な汎用処理では、ParameterInfoを調べて代入可能性を判定する処理が必要です。

6-4. 属性情報を読み取るサンプル

プロパティに表示名属性を付け、一覧として読み取る例です。

C#
[AttributeUsage(AttributeTargets.Property)]
public sealed class LabelAttribute : Attribute
{
public LabelAttribute(string text)
{
Text = text;
}

public string Text { get; }
}
C#
public class Employee
{
[Label("社員番号")]
public int Id { get; set; }

[Label("氏名")]
public string Name { get; set; } = string.Empty;
}
C#
static void PrintLabels<T>()
{
foreach (PropertyInfo property in typeof(T).GetProperties())
{
LabelAttribute? label =
property.GetCustomAttribute<LabelAttribute>();

string displayName = label?.Text ?? property.Name;

Console.WriteLine(
$"{property.Name}: {displayName}");
}
}
C#
PrintLabels<Employee>();

独自属性を使うことで、表示名、入力チェック、DBカラム名、権限情報などのメタデータを宣言的に付与できます。

6-5. JSON変換・DI・ORMで使われる例

JSON変換では、リフレクションでプロパティを取得し、名前と値を読み取る処理が基本になります。

C#
foreach (PropertyInfo property in target.GetType().GetProperties())
{
string name = property.Name;
object? value = property.GetValue(target);

// nameとvalueをJSONのキー・値へ変換する
}

DIコンテナでは、コンストラクタの引数を調べ、必要なサービスを解決します。

C#
ConstructorInfo? constructor = serviceType
.GetConstructors()
.OrderByDescending(x => x.GetParameters().Length)
.FirstOrDefault();

ParameterInfo[] parameters =
constructor?.GetParameters() ?? Array.Empty<ParameterInfo>();

ORMでは、取得したデータをプロパティに設定します。

C#
PropertyInfo? property = entityType.GetProperty(columnName);

if (property is not null && property.CanWrite)
{
property.SetValue(entity, databaseValue);
}

実際のライブラリでは、命名規則、属性、null許容型、型変換、継承、キャッシュなどを組み合わせ、より複雑な処理を行っています。

7. C#リフレクションを使うときの注意点

7-1. パフォーマンスへの影響

リフレクションによるメンバー検索やInvokeGetValueSetValueは、通常の直接呼び出しよりオーバーヘッドが大きくなります。

特に、ループ内で毎回GetPropertyGetMethodを実行すると負荷が高くなりやすいため、取得した情報をキャッシュする方法が有効です。

C#
private static readonly PropertyInfo? NameProperty =
typeof(User).GetProperty(nameof(User.Name));

複数の型を扱う場合は、辞書などに保存できます。

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

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

return properties;
}
}

並行処理を考慮する場合は、ConcurrentDictionaryも利用できます。

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

static PropertyInfo[] GetCachedProperties(Type type)
{
return PropertyCache.GetOrAdd(
type,
static key => key.GetProperties());
}

高頻度で値を取得・設定する処理では、式ツリーやデリゲートを生成してキャッシュする方法もあります。

7-2. 型安全性が低くなるリスク

リフレクションでは、メンバー名を文字列で指定することが多くなります。

C#
typeof(User).GetProperty("Nmae");

このコードにはスペルミスがありますが、コンパイルエラーにはなりません。実行するとnullが返されます。

可能な場合は、nameofを利用すると安全性が高まります。

C#
typeof(User).GetProperty(nameof(User.Name));

値の取得結果もobjectになるため、キャスト時の型違いに注意が必要です。

C#
object? value = property.GetValue(user);

if (value is string name)
{
Console.WriteLine(name);
}

通常のプロパティアクセスやインターフェースで実装できる処理まで、無理にリフレクションへ置き換えないことが重要です。

7-3. privateメンバーへのアクセスに関する注意

privateメンバーは、クラスの外部から利用されることを前提としていません。

リフレクションでprivateメンバーへアクセスすると、次のような問題が起こる可能性があります。

  • クラスの内部状態が不正になる

  • 実装変更によって突然動かなくなる

  • メンバー名の変更をコンパイラが検出できない

  • カプセル化が崩れ、保守性が低下する

  • 実行環境やセキュリティ設定の影響を受ける

テストコードで利用する場合も、privateメソッドを直接テストしなければならない設計になっていないか検討する必要があります。

7-4. 例外が発生しやすいポイント

リフレクションでは、次のような例外が発生する可能性があります。

例外主な原因
AmbiguousMatchException同名のメンバーが複数一致した
TargetParameterCountException渡した引数の数が一致しない
ArgumentException引数や設定値の型が一致しない
TargetException対象インスタンスが不正
TargetInvocationException呼び出したメソッド内部で例外が発生した
MissingMethodException対応するコンストラクタなどが見つからない
MemberAccessExceptionメンバーへのアクセスやインスタンス生成ができない

Invokeで呼び出したメソッド内の例外は、TargetInvocationExceptionに包まれることがあります。実際の原因はInnerExceptionで確認します。

C#
try
{
method.Invoke(target, arguments);
}
catch (TargetInvocationException ex)
{
Exception actualException = ex.InnerException ?? ex;
Console.WriteLine(actualException.Message);
}

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

リフレクションが適しているのは、次のようなケースです。

  • 実行時まで対象の型が決まらない

  • 多数の型を共通ルールで処理する

  • 属性を使って処理内容を切り替える

  • 外部アセンブリからプラグインを読み込む

  • フレームワークや汎用ライブラリを開発する

  • シリアライザー、DI、ORMのような仕組みを作る

一方、次のようなケースでは避けたほうがよいでしょう。

  • 対象の型がコンパイル時に決まっている

  • 通常のプロパティアクセスで実装できる

  • インターフェースやジェネリック型で解決できる

  • 高頻度で呼び出される性能重視の処理

  • privateメンバーへ依存しなければ実装できない

リフレクションは、通常の型システムでは実現しにくい動的処理に限定して利用することが大切です。

8. C#リフレクションでよくあるエラーと対処法

8-1. GetPropertyがnullになる原因

GetPropertynullを返す主な原因は次のとおりです。

  • プロパティ名が間違っている

  • 大文字と小文字が一致していない

  • privateプロパティなのにBindingFlags.NonPublicを指定していない

  • インスタンスプロパティなのにBindingFlags.Instanceを指定していない

  • staticプロパティなのにBindingFlags.Staticを指定していない

  • プロパティではなくフィールドとして定義されている

  • 対象にしている型が違う

まず、プロパティ一覧を出力すると原因を確認しやすくなります。

C#
foreach (PropertyInfo property in type.GetProperties(
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.Static))
{
Console.WriteLine(property.Name);
}

また、文字列リテラルではなくnameofを使うと、名前の間違いを防ぎやすくなります。

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

8-2. InvokeでTargetParameterCountExceptionが出る原因

TargetParameterCountExceptionは、メソッドに渡した引数の数が定義と一致しない場合に発生します。

C#
MethodInfo? method = typeof(Calculator).GetMethod("Add");

// Addには2つの引数が必要
method?.Invoke(new Calculator(), new object?[] { 10 });

実行前にGetParametersで必要な引数を確認できます。

C#
ParameterInfo[] parameters =
method?.GetParameters() ?? Array.Empty<ParameterInfo>();

Console.WriteLine($"必要な引数の数: {parameters.Length}");

省略可能な引数があっても、リフレクション経由の呼び出しでは渡し方に注意が必要です。既定値を使用させる場合は、Type.Missingを渡す方法があります。

C#
method?.Invoke(target, new object?[] { Type.Missing });

8-3. MethodInfoが取得できない原因

GetMethodMethodInfoを取得できない主な原因は、次のとおりです。

  • メソッド名が間違っている

  • アクセス修飾子に合うBindingFlagsを指定していない

  • staticとinstanceの指定が違う

  • オーバーロードの引数型が一致していない

  • 対象の型を間違えている

  • ジェネリックメソッドを通常のメソッドと同じ方法で探している

オーバーロードがある場合は、引数型を明示します。

C#
MethodInfo? method = typeof(Formatter).GetMethod(
"Format",
BindingFlags.Public | BindingFlags.Instance,
binder: null,
types: new[] { typeof(int) },
modifiers: null);

ジェネリックメソッドを探す場合は、候補を列挙して条件を指定します。

C#
MethodInfo? method = typeof(SampleService)
.GetMethods()
.FirstOrDefault(x =>
x.Name == "Convert" &&
x.IsGenericMethodDefinition &&
x.GetGenericArguments().Length == 1);

8-4. BindingFlagsの指定ミス

BindingFlagsを指定したのにメンバーが取得できない場合は、組み合わせを確認します。

privateなインスタンスメンバーを取得する例です。

C#
BindingFlags flags =
BindingFlags.NonPublic |
BindingFlags.Instance;

publicとprivateの両方を取得する例です。

C#
BindingFlags flags =
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance;

staticメンバーも含める場合は、BindingFlags.Staticを追加します。

C#
BindingFlags flags =
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.Static;

BindingFlagsを明示した場合、PublicまたはNonPublicと、InstanceまたはStaticの組み合わせが不足していないか確認しましょう。

8-5. privateメンバーにアクセスできない場合の確認点

privateメンバーを取得できない場合は、次の点を確認します。

  • BindingFlags.NonPublicを指定しているか

  • BindingFlags.InstanceまたはBindingFlags.Staticを指定しているか

  • メンバー名が正しいか

  • privateメンバーが基底クラスに定義されていないか

  • プロパティ、フィールド、メソッドの種類を間違えていないか

基底クラスのprivateメンバーは、派生クラスのTypeから単純に検索しても取得できない場合があります。その場合は、基底クラスを順番に調べます。

C#
static FieldInfo? FindPrivateField(
Type type,
string fieldName)
{
for (Type? current = type;
current is not null;
current = current.BaseType)
{
FieldInfo? field = current.GetField(
fieldName,
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.DeclaredOnly);

if (field is not null)
{
return field;
}
}

return null;
}

privateメンバーを無理に取得する前に、publicなAPIやインターフェースで代替できないかも確認しましょう。

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

9-1. リフレクションはいつ使うべき?

実行時まで対象の型が決まらない場合や、多数の異なる型を共通の仕組みで処理する場合に適しています。

代表例は、JSON変換、DI、ORM、テストフレームワーク、プラグイン読み込み、属性ベースの処理です。

対象の型やメソッドがコンパイル時に決まっている場合は、通常のメソッド呼び出し、インターフェース、ジェネリック型などを優先したほうが安全で高速です。

9-2. typeofとGetTypeの違いは?

typeofは、型名からTypeを取得します。

C#
Type type = typeof(User);

GetTypeは、インスタンスから実行時の型を取得します。

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

継承関係がある場合、違いが明確になります。

C#
Animal animal = new Dog();

Console.WriteLine(typeof(Animal).Name);
Console.WriteLine(animal.GetType().Name);

typeof(Animal)Animalを返し、animal.GetType()は実際のインスタンス型であるDogを返します。

9-3. privateメソッドやprivateプロパティは取得できる?

取得できます。BindingFlags.NonPublicBindingFlags.InstanceまたはBindingFlags.Staticを組み合わせます。

C#
MethodInfo? method = typeof(SampleService).GetMethod(
"PrivateMethod",
BindingFlags.NonPublic | BindingFlags.Instance);
C#
PropertyInfo? property = typeof(SampleService).GetProperty(
"PrivateProperty",
BindingFlags.NonPublic | BindingFlags.Instance);

ただし、privateメンバーは内部実装であり、変更される可能性があります。アプリケーションコードから継続的に依存する設計は避けるべきです。

9-4. リフレクションは遅い?

通常のプロパティアクセスやメソッド呼び出しと比べると、リフレクションには追加の処理コストがあります。

特に、次の処理を大量に繰り返すと影響が大きくなります。

  • GetPropertyGetMethodによる検索

  • PropertyInfo.GetValue

  • PropertyInfo.SetValue

  • MethodInfo.Invoke

  • Activator.CreateInstance

処理回数が少ない設定読み込みや起動時の型検索では、問題にならないことも多くあります。一方、大量データを処理するループ内では、PropertyInfoMethodInfoをキャッシュするなどの対策が必要です。

9-5. .NET Coreや.NET 8でもリフレクションは使える?

C#のリフレクションは、.NET Frameworkだけでなく、.NET Coreや.NET 8を含む現在の.NETでも利用できます。

基本的なTypePropertyInfoMethodInfoAssemblyBindingFlagsなどのAPIは、現在の.NETでも使用可能です。

ただし、トリミングやNative AOTを利用するアプリケーションでは注意が必要です。リフレクションからしか参照されない型やメンバーは、ビルド時に未使用と判断されて削除される可能性があります。

そのような環境では、必要に応じて属性やリンカー設定で対象メンバーを保持したり、ソースジェネレーターを利用したりする設計を検討します。

まとめ

C#のリフレクションは、プログラムの実行中に型、プロパティ、メソッド、フィールド、コンストラクタ、属性などの情報を取得し、動的に操作する機能です。

型情報の取得にはTypeを使い、対象に応じて次のAPIを利用します。

  • プロパティの取得にはGetPropertiesGetProperty

  • プロパティ値の操作にはGetValueSetValue

  • メソッドの取得にはGetMethodsGetMethod

  • メソッドの実行にはInvoke

  • フィールドの取得にはGetFieldsGetField

  • コンストラクタの取得にはGetConstructors

  • 動的なインスタンス生成にはActivator.CreateInstance

  • 属性の取得にはGetCustomAttributesGetCustomAttribute

リフレクションを使えば、実行時に型が決まる柔軟な処理を実装できます。その一方で、通常のコードより型安全性が低く、処理コストや保守性にも注意が必要です。

通常のプロパティアクセス、インターフェース、ジェネリック型などで実装できる場合は、それらを優先しましょう。リフレクションは、JSON変換、DI、ORM、属性の読み取り、プラグイン機能など、動的な型操作が本当に必要な場面で活用することが重要です。