C# PropertyInfoの使い方完全ガイド|プロパティの取得・値の読み書き・Reflection実装例まで解説

はじめに

C#でアプリケーションを開発していると、「プロパティ名を文字列で指定して値を取得したい」「DTOの値をまとめてコピーしたい」「属性を見て動的に処理を変えたい」といった場面があります。通常のコードでは user.Name のようにコンパイル時に決まったプロパティへアクセスしますが、実行時に型情報を調べてプロパティを操作したい場合は、Reflectionと PropertyInfo が役立ちます。

PropertyInfo は、C#のプロパティに関するメタデータを表すクラスです。プロパティ名、型、読み取り可否、書き込み可否、属性、getter/setter、現在の値などを実行時に扱えます。Microsoft Learnでも、PropertyInfo はプロパティの属性を検出し、プロパティメタデータへのアクセスを提供する型として説明されています。Microsoft Learn

この記事では、C#の PropertyInfo の基本から、GetPropertyGetPropertiesGetValueSetValueBindingFlags、属性取得、実践的なコピー処理やDictionary変換まで、実装例を交えて詳しく解説します。

1. C# PropertyInfoとは?Reflectionでプロパティ情報を扱う基本

1-1. PropertyInfoの役割とできること

PropertyInfo は、System.Reflection 名前空間に含まれるクラスで、クラスや構造体に定義されたプロパティの情報を表します。

たとえば、次のようなクラスがあるとします。

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

通常は次のようにアクセスします。

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

Console.WriteLine(user.Name);

一方、PropertyInfo を使うと、プロパティ名を文字列として扱いながら値を取得できます。

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

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

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

Console.WriteLine(value);

このように、PropertyInfo を使うと、実行時にプロパティを調べたり、値を読み取ったり、値を設定したりできます。GetValue は指定したオブジェクトのプロパティ値を返すメソッドであり、SetValue は指定したオブジェクトのプロパティ値を設定するメソッドです。Microsoft Learn+1

1-2. Reflectionとは何か

Reflectionとは、実行時に型、メンバー、メソッド、プロパティ、属性などの情報を調べたり操作したりする仕組みです。

通常、C#ではコンパイル時に型が決まります。しかしReflectionを使うと、実行時に次のような処理ができます。

C#
Type type = typeof(User);

Console.WriteLine(type.Name);

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

Reflectionでは、アセンブリ、モジュール、型、メンバーを表すオブジェクトを通じて、メソッドの呼び出しやフィールド・プロパティへのアクセスができます。Microsoft Learn

1-3. PropertyInfoとType・MemberInfoの関係

PropertyInfo を理解するには、TypeMemberInfo との関係を押さえておくと分かりやすくなります。

Type は、クラスや構造体などの型情報を表します。

C#
Type type = typeof(User);

Type から GetPropertyGetProperties を呼び出すことで、PropertyInfo を取得できます。

C#
PropertyInfo? property = type.GetProperty("Name");
PropertyInfo[] properties = type.GetProperties();

また、PropertyInfoMemberInfo を継承しています。MemberInfo は、プロパティ、メソッド、フィールド、イベントなど、型に属するメンバー情報の共通基底クラスです。

つまり、関係性は次のように考えられます。

Type
└─ MemberInfo
└─ PropertyInfo

Type から型全体の情報を取得し、その中のプロパティ情報を PropertyInfo として扱う、という流れです。

1-4. FieldInfo・MethodInfoとの違い

Reflectionには、PropertyInfo 以外にも FieldInfoMethodInfo があります。

FieldInfo はフィールド情報を扱う型です。

C#
public class Sample
{
public string name = "";
}

この name はフィールドなので、Reflectionでは FieldInfo で扱います。

C#
FieldInfo? field = typeof(Sample).GetField("name");

一方、PropertyInfo はプロパティを扱います。

C#
public class Sample
{
public string Name { get; set; } = "";
}

この Name はプロパティなので、PropertyInfo で扱います。

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

MethodInfo はメソッドを扱います。

C#
public class Sample
{
public void Execute()
{
}
}
C#
MethodInfo? method = typeof(Sample).GetMethod("Execute");

整理すると、次のようになります。

種類対象代表的な取得メソッド
PropertyInfoプロパティGetProperty / GetProperties
FieldInfoフィールドGetField / GetFields
MethodInfoメソッドGetMethod / GetMethods

C#ではプロパティとフィールドは似て見えることがありますが、Reflectionでは別のメンバーとして扱います。

1-5. PropertyInfoを使う主な場面

PropertyInfo は、次のような場面でよく使われます。

DTOやViewModelの値を動的にコピーしたい場合、プロパティ一覧を取得して同名プロパティへ値を設定できます。

C#
foreach (var sourceProperty in source.GetType().GetProperties())
{
var targetProperty = target.GetType().GetProperty(sourceProperty.Name);
if (targetProperty != null && targetProperty.CanWrite)
{
var value = sourceProperty.GetValue(source);
targetProperty.SetValue(target, value);
}
}

ログ出力やデバッグ用に、オブジェクトの全プロパティ名と値を一覧化したい場合にも便利です。

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

属性を使った動的マッピング、CSV出力、バリデーション、テスト補助、シリアライズ処理などでも PropertyInfo が活用されます。

2. PropertyInfoを使うための準備

2-1. System.Reflection名前空間をusingする

PropertyInfo を使うには、基本的に System.Reflection 名前空間を using します。

C#
using System.Reflection;

ただし、typeof(User).GetProperty("Name") のような短いコードでは、型推論によって PropertyInfo 型を明示しない限り using が不要に見えることもあります。実務では PropertyInfoBindingFlagsMethodInfo などを明示的に使うことが多いため、最初に using System.Reflection; を書いておくと分かりやすいです。

2-2. 対象クラスのサンプル定義

この記事では、次の User クラスをサンプルとして使います。

C#
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

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

[DisplayName("ユーザー名")]
[Required]
public string Name { get; set; } = "";

public int? Age { get; set; }

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

private string SecretCode { get; set; } = "SECRET";

public static string AppName { get; set; } = "SampleApp";

public string this[int index]
{
get => $"{Name}-{index}";
}

public User()
{
}

public User(string email)
{
Email = email;
}
}

このクラスには、publicプロパティ、privateプロパティ、private setプロパティ、staticプロパティ、Nullableプロパティ、属性付きプロパティ、indexerを含めています。

2-3. Typeオブジェクトを取得する方法

PropertyInfo を取得するには、まず Type オブジェクトを取得します。

代表的な方法は2つあります。

C#
Type type1 = typeof(User);
C#
var user = new User();
Type type2 = user.GetType();

typeof(User) は、コンパイル時に型が分かっている場合に使います。

C#
Type type = typeof(User);

GetType() は、実行時に存在するインスタンスから型を取得する場合に使います。

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

2-4. typeofとGetTypeの違い

typeofGetType() はどちらも Type を取得しますが、使いどころが異なります。

typeof は型名を直接指定します。

C#
Type type = typeof(User);

この方法は、対象の型がコード上で明確な場合に適しています。

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

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

この方法は、変数の宣言型ではなく、実体の型を知りたい場合に使います。

たとえば、基底型の変数に派生型のインスタンスが入っている場合、GetType() は実際の派生型を返します。

C#
object obj = new User();

Console.WriteLine(obj.GetType().Name); // User

Reflectionで動的処理を行う場合は、対象オブジェクトを受け取って obj.GetType() からプロパティ一覧を取得する実装がよく使われます。

2-5. PropertyInfo操作の基本的な流れ

PropertyInfo 操作の基本的な流れは、次の通りです。

C#
using System.Reflection;

var user = new User
{
Id = 1,
Name = "Taro",
Age = 30
};

Type type = user.GetType();

PropertyInfo? property = type.GetProperty("Name");

if (property != null && property.CanRead)
{
object? value = property.GetValue(user);
Console.WriteLine(value);
}

基本は次の4ステップです。

  1. 対象のインスタンスを用意する

  2. Type を取得する

  3. GetProperty または GetPropertiesPropertyInfo を取得する

  4. GetValueSetValue で値を読み書きする

特に重要なのは、GetProperty が見つからない場合に null を返すことです。存在しないプロパティ名を指定した場合に備えて、必ず null チェックを行いましょう。Type.GetProperty は条件に一致するプロパティが見つからない場合、PropertyInfo ではなく null を返します。Microsoft Learn

3. PropertyInfoでプロパティを取得する方法

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

特定のプロパティを取得するには、Type.GetProperty を使います。

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

if (property != null)
{
Console.WriteLine(property.Name);
}

GetProperty(string name) は、指定した名前のpublicプロパティを検索します。Microsoft Learnでも、GetProperty(String) は指定した名前のpublicプロパティを検索すると説明されています。Microsoft Learn

値を取得する場合は、次のようにします。

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

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

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

3-2. GetPropertiesで全プロパティを取得する

全てのpublicプロパティを取得するには、GetProperties() を使います。

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

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

出力例は次のようになります。

Id
Name
Age
Email
AppName
Item

C#のindexerはReflection上では通常 Item という名前のプロパティとして扱われます。indexerかどうかを判定したい場合は、GetIndexParameters() を使います。

C#
foreach (PropertyInfo property in typeof(User).GetProperties())
{
bool isIndexer = property.GetIndexParameters().Length > 0;
Console.WriteLine($"{property.Name}: Indexer={isIndexer}");
}

3-3. プロパティ名で検索する実装例

プロパティ名を指定して検索する汎用メソッドを作ると便利です。

C#
using System.Reflection;

public static PropertyInfo? FindProperty(Type type, string propertyName)
{
return type.GetProperty(propertyName);
}

大文字・小文字を無視したい場合は、BindingFlags.IgnoreCase を指定します。

C#
public static PropertyInfo? FindPropertyIgnoreCase(Type type, string propertyName)
{
return type.GetProperty(
propertyName,
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.IgnoreCase);
}

使用例です。

C#
PropertyInfo? property = FindPropertyIgnoreCase(typeof(User), "name");

if (property != null)
{
Console.WriteLine(property.Name); // Name
}

ただし、大文字・小文字を無視すると、似た名前のプロパティがある場合に意図しないプロパティを取得する可能性があります。基本的には nameof(User.Name) のように正確な名前を指定する方が安全です。

3-4. publicプロパティを取得する方法

publicインスタンスプロパティを取得するには、次のようにします。

C#
PropertyInfo[] properties = typeof(User).GetProperties(
BindingFlags.Public | BindingFlags.Instance);

特定のpublicインスタンスプロパティを取得する場合は、次のようにします。

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

BindingFlags を指定する場合は、基本的に「publicかnon-publicか」「instanceかstaticか」を組み合わせて指定します。

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

privateプロパティやprotectedプロパティを取得するには、BindingFlags.NonPublic を使います。

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

if (property != null)
{
Console.WriteLine(property.Name);
}

privateプロパティを含めて一覧取得したい場合は、次のようにします。

C#
PropertyInfo[] properties = typeof(User).GetProperties(
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance);

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

privateメンバーにアクセスする処理は、ライブラリや実行環境、セキュリティ要件によって問題になる場合があります。業務コードでは、privateメンバーに依存する設計はできるだけ避けるのが基本です。

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

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

C#
PropertyInfo? property = typeof(User).GetProperty(
"AppName",
BindingFlags.Public | BindingFlags.Static);

if (property != null)
{
Console.WriteLine(property.Name);
}

staticプロパティの値を取得する場合、GetValue の対象インスタンスには null を指定します。

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

Console.WriteLine(value);

値を設定する場合も同様です。

C#
property?.SetValue(null, "NewApp");

3-7. BindingFlagsの使い方と指定例

BindingFlags は、Reflectionでメンバーを検索するときの条件を指定する列挙型です。たとえば、public、non-public、instance、static、大文字小文字無視などを指定できます。

よく使う組み合わせは次の通りです。

C#
// publicインスタンスプロパティ
BindingFlags.Public | BindingFlags.Instance
C#
// private/protectedインスタンスプロパティ
BindingFlags.NonPublic | BindingFlags.Instance
C#
// public staticプロパティ
BindingFlags.Public | BindingFlags.Static
C#
// public/privateを含むインスタンスプロパティ
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
C#
// 大文字・小文字を無視して検索
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase

BindingFlags.GetPropertyBindingFlags.SetProperty は、主に InvokeMember を使ってプロパティgetter/setterを呼び出す場合に使われるフラグです。通常の GetPropertyGetValueSetValue の基本操作では、PublicNonPublicInstanceStatic の組み合わせを使うことが多いです。Microsoft Learnでも、BindingFlags.GetProperty は指定したプロパティの値を返すために InvokeMember に渡されるフラグとして説明されています。Microsoft Learn

3-8. 存在しないプロパティを取得した場合の注意点

存在しないプロパティ名を指定すると、GetPropertynull を返します。

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

Console.WriteLine(property == null); // True

そのため、次のようなコードは危険です。

C#
PropertyInfo property = typeof(User).GetProperty("NotExists")!;
object? value = property.GetValue(new User());

null を無理に無視すると、実行時に NullReferenceException が発生します。

安全に扱うには、必ず null チェックを行います。

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

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

プロパティ名を文字列で扱うと、タイポやリファクタリング漏れが発生しやすくなります。可能であれば、nameof(User.Name) を使いましょう。

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

4. PropertyInfoからプロパティ情報を取得する

4-1. Nameでプロパティ名を取得する

PropertyInfo.Name を使うと、プロパティ名を取得できます。

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

if (property != null)
{
Console.WriteLine(property.Name);
}

全プロパティ名を一覧表示する例です。

C#
foreach (PropertyInfo property in typeof(User).GetProperties())
{
Console.WriteLine(property.Name);
}

ログ出力、CSVヘッダー生成、Dictionary変換、汎用的なデバッグ表示などでよく使います。

4-2. PropertyTypeで型情報を取得する

PropertyInfo.PropertyType を使うと、プロパティの型を表す Type を取得できます。Microsoft Learnでも、対象の PropertyInfo を取得した後、PropertyType から型情報を取得する流れが説明されています。Microsoft Learn

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

if (property != null)
{
Type propertyType = property.PropertyType;
Console.WriteLine(propertyType.Name);
}

int の場合は Int32string の場合は Stringint? の場合は Nullable1` のような型名になります。

型判定の例です。

C#
if (property?.PropertyType == typeof(string))
{
Console.WriteLine("string型です。");
}

Nullable型を判定する場合は、次のようにします。

C#
Type? underlyingType = Nullable.GetUnderlyingType(property.PropertyType);

if (underlyingType != null)
{
Console.WriteLine($"Nullable型です。元の型: {underlyingType.Name}");
}

4-3. CanRead・CanWriteで読み書き可能か判定する

CanRead はgetterが存在するかどうか、CanWrite はsetterが存在するかどうかを示します。

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

if (property != null)
{
Console.WriteLine(property.CanRead);
Console.WriteLine(property.CanWrite);
}

値を読み取る前には CanRead を確認すると安全です。

C#
if (property != null && property.CanRead)
{
object? value = property.GetValue(user);
}

値を設定する前には CanWrite を確認します。

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

ただし、CanWritetrue でも、setterがprivateの場合や実行環境によってはアクセス時に例外が発生することがあります。完全に安全にするには、SetMethod のアクセス修飾子も確認するとよいでしょう。

4-4. GetMethod・SetMethodでアクセサ情報を取得する

GetMethodSetMethod を使うと、プロパティのgetter/setterに対応する MethodInfo を取得できます。

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

if (property != null)
{
MethodInfo? getMethod = property.GetMethod;
MethodInfo? setMethod = property.SetMethod;

Console.WriteLine($"Getter: {getMethod?.Name}");
Console.WriteLine($"Setter: {setMethod?.Name}");
}

setterがprivateかどうかを判定する例です。

C#
if (property?.SetMethod != null)
{
Console.WriteLine(property.SetMethod.IsPrivate);
}

staticプロパティかどうかを判定する場合にも、getterまたはsetterの IsStatic を利用できます。

C#
bool isStatic =
property?.GetMethod?.IsStatic == true ||
property?.SetMethod?.IsStatic == true;

4-5. IsStaticを判定する方法

PropertyInfo 自体には IsStatic という直接のプロパティはありません。そのため、getterまたはsetterの MethodInfo.IsStatic を確認します。

C#
public static bool IsStaticProperty(PropertyInfo property)
{
return property.GetMethod?.IsStatic == true ||
property.SetMethod?.IsStatic == true;
}

使用例です。

C#
foreach (PropertyInfo property in typeof(User).GetProperties(
BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance))
{
Console.WriteLine($"{property.Name}: Static={IsStaticProperty(property)}");
}

staticプロパティはインスタンスに属する値ではなく型に属する値なので、GetValueSetValue では対象オブジェクトに null を指定します。

4-6. カスタム属性を取得する方法

プロパティに付与された属性を取得するには、GetCustomAttributes または GetCustomAttribute<T> を使います。C#では属性とReflectionを組み合わせることで、コード上に付与したメタデータを実行時に取得できます。Microsoft Learn

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

if (property != null)
{
object[] attributes = property.GetCustomAttributes(inherit: true);

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

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

C#
DisplayNameAttribute? displayName =
property?.GetCustomAttribute<DisplayNameAttribute>();

if (displayName != null)
{
Console.WriteLine(displayName.DisplayName);
}

4-7. Nullable型やジェネリック型の扱い方

Nullable型を扱う場合は、Nullable.GetUnderlyingType を使います。

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

if (property != null)
{
Type type = property.PropertyType;
Type? nullableType = Nullable.GetUnderlyingType(type);

if (nullableType != null)
{
Console.WriteLine($"Nullable<{nullableType.Name}>");
}
}

ジェネリック型かどうかは、IsGenericType で判定します。

C#
if (property.PropertyType.IsGenericType)
{
Console.WriteLine("ジェネリック型です。");
}

たとえば、List<string> の要素型を取得する場合は次のようにします。

C#
public class Group
{
public List<string> Members { get; set; } = new();
}

PropertyInfo? property = typeof(Group).GetProperty(nameof(Group.Members));

if (property != null && property.PropertyType.IsGenericType)
{
Type[] arguments = property.PropertyType.GetGenericArguments();

foreach (Type argument in arguments)
{
Console.WriteLine(argument.Name); // String
}
}

Nullable型やジェネリック型を正しく扱うことで、動的な型変換やCSV出力、バリデーション処理をより安全に実装できます。

5. PropertyInfoでプロパティの値を読み取る方法

5-1. GetValueの基本的な使い方

プロパティの値を取得するには、PropertyInfo.GetValue を使います。

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

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

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

GetValue(Object) は、指定したオブジェクトのプロパティ値を返します。戻り値の型は object? になるため、必要に応じてキャストします。Microsoft Learn

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

値型の場合は、明示的にキャストできます。

C#
PropertyInfo? idProperty = typeof(User).GetProperty(nameof(User.Id));

if (idProperty != null)
{
int id = (int)idProperty.GetValue(user)!;
Console.WriteLine(id);
}

5-2. インスタンスプロパティの値を取得する

インスタンスプロパティの値を取得する場合は、GetValue に対象インスタンスを渡します。

C#
var user = new User
{
Id = 10,
Name = "Hanako"
};

PropertyInfo? property = typeof(User).GetProperty(nameof(User.Id));

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

Console.WriteLine(value);

対象オブジェクトの型と PropertyInfo を取得した型が一致していない場合、例外が発生することがあります。たとえば、typeof(User) から取得した PropertyInfo に対して、別の型のオブジェクトを渡すのは誤りです。

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

object other = new object();

// property?.GetValue(other); // 不正

5-3. staticプロパティの値を取得する

staticプロパティの値を取得する場合は、GetValue の引数に null を指定します。

C#
PropertyInfo? property = typeof(User).GetProperty(
nameof(User.AppName),
BindingFlags.Public | BindingFlags.Static);

if (property != null)
{
object? value = property.GetValue(null);
Console.WriteLine(value);
}

staticプロパティはインスタンスではなく型に属しているため、インスタンスを渡す必要はありません。

5-4. privateプロパティの値を取得する

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

C#
var user = new User();

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

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

privateプロパティへのアクセスは、テストや移行処理など限定的な場面では便利ですが、通常のアプリケーションロジックで多用するのは避けた方がよいです。privateメンバーに依存すると、クラス内部の実装変更によって外部コードが壊れやすくなります。

5-5. indexerプロパティの値を取得する

C#のindexerは、Reflectionではインデックス付きプロパティとして扱われます。値を取得するには、GetValue の第2引数にインデックス値の配列を渡します。

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

PropertyInfo? indexer = typeof(User).GetProperty("Item");

if (indexer != null)
{
object? value = indexer.GetValue(user, new object[] { 3 });
Console.WriteLine(value); // Taro-3
}

通常のプロパティかindexerかを判定するには、GetIndexParameters() を使います。

C#
bool isIndexer = indexer?.GetIndexParameters().Length > 0;

GetValue(Object, Object[]) は、インデックス付きプロパティの任意のインデックス値を指定して値を取得するためのオーバーロードです。Microsoft Learn

5-6. nullや例外が発生するケース

GetValue を使うときは、次のようなケースに注意が必要です。

まず、GetPropertynull を返す場合があります。

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

この状態で property.GetValue(...) を呼ぶと、NullReferenceException が発生します。

次に、対象オブジェクトが null の場合も注意が必要です。インスタンスプロパティに対して null を渡すと、正しく値を取得できません。

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

// property?.GetValue(null); // インスタンスプロパティでは不正

また、プロパティのgetter内部で例外が発生した場合、Reflection経由では TargetInvocationException としてラップされることがあります。

C#
public class ErrorSample
{
public string Name => throw new InvalidOperationException("取得できません。");
}
C#
try
{
var obj = new ErrorSample();
PropertyInfo? property = typeof(ErrorSample).GetProperty(nameof(ErrorSample.Name));
object? value = property?.GetValue(obj);
}
catch (TargetInvocationException ex)
{
Console.WriteLine(ex.InnerException?.Message);
}

5-7. GetValueを安全に使う実装例

安全に値を取得する汎用メソッドの例です。

C#
using System.Reflection;

public static bool TryGetPropertyValue(
object target,
string propertyName,
out object? value)
{
value = null;

if (target == null)
{
return false;
}

PropertyInfo? property = target.GetType().GetProperty(
propertyName,
BindingFlags.Public | BindingFlags.Instance);

if (property == null || !property.CanRead)
{
return false;
}

try
{
value = property.GetValue(target);
return true;
}
catch
{
return false;
}
}

使用例です。

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

if (TryGetPropertyValue(user, nameof(User.Name), out object? value))
{
Console.WriteLine(value);
}

例外を握りつぶすべきかどうかは要件によります。ライブラリコードでは、失敗理由が分かるように結果型や独自例外を使う設計も有効です。

6. PropertyInfoでプロパティの値を書き換える方法

6-1. SetValueの基本的な使い方

プロパティの値を設定するには、PropertyInfo.SetValue を使います。

C#
var user = new User();

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

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

Console.WriteLine(user.Name);

SetValue(Object, Object) は、指定したオブジェクトのプロパティ値を設定するためのオーバーロードです。インデックスなしのプロパティに対して使用できます。Microsoft Learn

6-2. インスタンスプロパティに値を設定する

インスタンスプロパティに値を設定するには、対象インスタンスを第1引数に渡します。

C#
var user = new User();

PropertyInfo? property = typeof(User).GetProperty(nameof(User.Age));

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

Console.WriteLine(user.Age);

SetValue の第2引数は object? なので、実際のプロパティ型に代入可能な値を渡す必要があります。

C#
property.SetValue(user, 25);      // OK
// property.SetValue(user, "25"); // int?には直接代入できない

文字列から数値へ変換したい場合は、事前に型変換します。

6-3. staticプロパティに値を設定する

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

C#
PropertyInfo? property = typeof(User).GetProperty(
nameof(User.AppName),
BindingFlags.Public | BindingFlags.Static);

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

Console.WriteLine(User.AppName);

staticプロパティは型に属するため、インスタンスを作成する必要はありません。

6-4. private setプロパティに値を設定する

private set のプロパティは、通常のコードではクラス外から代入できません。

C#
public string Email { get; private set; } = "";

Reflectionを使うと、環境やアクセス条件によっては値を設定できる場合があります。

C#
var user = new User();

PropertyInfo? property = typeof(User).GetProperty(
nameof(User.Email),
BindingFlags.Public | BindingFlags.Instance);

if (property != null)
{
property.SetValue(user, "taro@example.com");
}

Console.WriteLine(user.Email);

setterがprivateかどうかを確認するには、SetMethod を見ます。

C#
if (property?.SetMethod?.IsPrivate == true)
{
Console.WriteLine("private setです。");
}

ただし、private setを書き換える設計は慎重に使うべきです。ドメインモデルの不変条件を壊したり、将来の実装変更で動かなくなったりする可能性があります。

6-5. 型変換が必要な場合の対応方法

SetValue では、渡す値の型がプロパティ型と互換性を持っている必要があります。文字列 "30"int プロパティへそのまま設定することはできません。

型変換を行う例です。

C#
public static object? ConvertValue(object? value, Type targetType)
{
if (value == null)
{
return null;
}

Type actualType = Nullable.GetUnderlyingType(targetType) ?? targetType;

if (actualType.IsEnum)
{
return Enum.Parse(actualType, value.ToString()!);
}

return Convert.ChangeType(value, actualType);
}

使用例です。

C#
var user = new User();

PropertyInfo? property = typeof(User).GetProperty(nameof(User.Age));

if (property != null && property.CanWrite)
{
object? convertedValue = ConvertValue("30", property.PropertyType);
property.SetValue(user, convertedValue);
}

Console.WriteLine(user.Age);

Nullable型の場合は、Nullable.GetUnderlyingType で元の型を取り出してから変換するのがポイントです。

6-6. 読み取り専用プロパティを書き換えられるか

getterのみのプロパティは、通常 SetValue では書き換えられません。

C#
public class ReadOnlySample
{
public string Name { get; } = "Default";
}

この場合、CanWritefalse になります。

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

Console.WriteLine(property?.CanWrite); // False

次のような処理は避けるべきです。

C#
// property?.SetValue(obj, "NewName"); // setterがないため不正

自動実装プロパティのバッキングフィールドをReflectionで探して書き換える方法も技術的には存在しますが、コンパイラ実装に依存し、保守性も低いため、通常のアプリケーションコードでは推奨されません。

6-7. SetValueで発生しやすい例外と対処法

SetValue でよく発生する例外には、次のようなものがあります。

例外主な原因
ArgumentException値の型がプロパティ型に代入できない
TargetException対象オブジェクトが不正
TargetInvocationExceptionsetter内部で例外が発生した
MethodAccessExceptionアクセスできないsetterを呼び出した
NullReferenceExceptionPropertyInfoがnullなのに使った

安全に設定する例です。

C#
public static bool TrySetPropertyValue(
object target,
string propertyName,
object? value)
{
if (target == null)
{
return false;
}

PropertyInfo? property = target.GetType().GetProperty(
propertyName,
BindingFlags.Public | BindingFlags.Instance);

if (property == null || !property.CanWrite)
{
return false;
}

try
{
object? convertedValue = ConvertValue(value, property.PropertyType);
property.SetValue(target, convertedValue);
return true;
}
catch
{
return false;
}
}

実務では、失敗した理由をログに残すとトラブルシューティングしやすくなります。

7. PropertyInfoの実践的な実装例

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

デバッグやログ出力では、オブジェクトの全プロパティ名と値を一覧表示したいことがあります。

C#
public static void PrintProperties(object obj)
{
if (obj == null)
{
return;
}

Type type = obj.GetType();

foreach (PropertyInfo property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!property.CanRead)
{
continue;
}

if (property.GetIndexParameters().Length > 0)
{
continue;
}

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

使用例です。

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

PrintProperties(user);

indexerは引数が必要なため、汎用的な一覧出力では除外しています。

7-2. プロパティ名を指定して値を取得する

プロパティ名を指定して値を取得するメソッドです。

C#
public static object? GetPropertyValue(object target, string propertyName)
{
if (target == null)
{
throw new ArgumentNullException(nameof(target));
}

PropertyInfo? property = target.GetType().GetProperty(
propertyName,
BindingFlags.Public | BindingFlags.Instance);

if (property == null)
{
throw new ArgumentException($"プロパティ '{propertyName}' が見つかりません。");
}

if (!property.CanRead)
{
throw new InvalidOperationException($"プロパティ '{propertyName}' は読み取れません。");
}

return property.GetValue(target);
}

使用例です。

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

object? value = GetPropertyValue(user, nameof(User.Name));

Console.WriteLine(value);

例外を返す設計にするか、TryGet... 形式にするかは利用箇所に合わせて選びます。

7-3. プロパティ名を指定して値を変更する

プロパティ名を指定して値を変更するメソッドです。

C#
public static void SetPropertyValue(
object target,
string propertyName,
object? value)
{
if (target == null)
{
throw new ArgumentNullException(nameof(target));
}

PropertyInfo? property = target.GetType().GetProperty(
propertyName,
BindingFlags.Public | BindingFlags.Instance);

if (property == null)
{
throw new ArgumentException($"プロパティ '{propertyName}' が見つかりません。");
}

if (!property.CanWrite)
{
throw new InvalidOperationException($"プロパティ '{propertyName}' は書き込めません。");
}

object? convertedValue = ConvertValue(value, property.PropertyType);

property.SetValue(target, convertedValue);
}

使用例です。

C#
var user = new User();

SetPropertyValue(user, nameof(User.Name), "Hanako");
SetPropertyValue(user, nameof(User.Age), "28");

Console.WriteLine(user.Name);
Console.WriteLine(user.Age);

文字列から数値へ変換する場合は、先ほどの ConvertValue を組み合わせます。

7-4. DTOやViewModelの値をコピーする

同名・同型のプロパティをコピーする実装例です。

C#
public static void CopyProperties(object source, object target)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}

if (target == null)
{
throw new ArgumentNullException(nameof(target));
}

Type sourceType = source.GetType();
Type targetType = target.GetType();

foreach (PropertyInfo sourceProperty in sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!sourceProperty.CanRead)
{
continue;
}

if (sourceProperty.GetIndexParameters().Length > 0)
{
continue;
}

PropertyInfo? targetProperty = targetType.GetProperty(
sourceProperty.Name,
BindingFlags.Public | BindingFlags.Instance);

if (targetProperty == null || !targetProperty.CanWrite)
{
continue;
}

if (!targetProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType))
{
continue;
}

object? value = sourceProperty.GetValue(source);
targetProperty.SetValue(target, value);
}
}

使用例です。

C#
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; } = "";
}

public class UserViewModel
{
public int Id { get; set; }
public string Name { get; set; } = "";
}

var dto = new UserDto { Id = 1, Name = "Taro" };
var viewModel = new UserViewModel();

CopyProperties(dto, viewModel);

Console.WriteLine(viewModel.Name);

この方法は簡単ですが、大量データを頻繁にコピーする場合はパフォーマンスに注意が必要です。AutoMapperのようなライブラリや、ソースジェネレーターを使う方法も検討できます。

7-5. Dictionaryにプロパティ値を変換する

オブジェクトを Dictionary<string, object?> に変換する例です。

C#
public static Dictionary<string, object?> ToDictionary(object obj)
{
if (obj == null)
{
throw new ArgumentNullException(nameof(obj));
}

var result = new Dictionary<string, object?>();

foreach (PropertyInfo property in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!property.CanRead)
{
continue;
}

if (property.GetIndexParameters().Length > 0)
{
continue;
}

result[property.Name] = property.GetValue(obj);
}

return result;
}

使用例です。

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

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

foreach (var item in dictionary)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}

APIレスポンスの整形、ログ出力、テンプレート置換などに応用できます。

7-6. CSV出力やログ出力で活用する

プロパティ一覧を使えば、簡易的なCSV出力も実装できます。

C#
public static string ToCsvLine<T>(T obj)
{
if (obj == null)
{
return "";
}

PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

var values = new List<string>();

foreach (PropertyInfo property in properties)
{
if (!property.CanRead || property.GetIndexParameters().Length > 0)
{
continue;
}

object? value = property.GetValue(obj);
string text = value?.ToString() ?? "";

text = text.Replace("\"", "\"\"");
values.Add($"\"{text}\"");
}

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

ヘッダー行を作る例です。

C#
public static string ToCsvHeader<T>()
{
PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

return string.Join(
",",
properties
.Where(p => p.CanRead && p.GetIndexParameters().Length == 0)
.Select(p => $"\"{p.Name}\""));
}

使用例です。

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

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

本格的なCSV出力では、改行、カンマ、エスケープ、文字コード、カルチャ、日付形式なども考慮する必要があります。

7-7. テストコードでPropertyInfoを使う例

テストコードでは、特定のプロパティが存在するか、属性が付いているか、読み書き可能かを確認するために PropertyInfo を使うことがあります。

C#
[Fact]
public void User_Name_Should_Have_RequiredAttribute()
{
PropertyInfo? property = typeof(User).GetProperty(nameof(User.Name));

Assert.NotNull(property);

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

Assert.NotNull(attribute);
}

publicプロパティがすべて読み書き可能かを確認する例です。

C#
[Fact]
public void User_PublicProperties_Should_Be_Readable()
{
PropertyInfo[] properties = typeof(User).GetProperties(
BindingFlags.Public | BindingFlags.Instance);

foreach (PropertyInfo property in properties)
{
if (property.GetIndexParameters().Length > 0)
{
continue;
}

Assert.True(property.CanRead, $"{property.Name} should be readable.");
}
}

ただし、テストでprivateメンバーへ過度に依存すると、内部実装の変更に弱いテストになります。基本的にはpublic APIの振る舞いをテストし、Reflectionは補助的に使うのがよいでしょう。

8. PropertyInfoと属性を組み合わせる使い方

8-1. GetCustomAttributesで属性を取得する

プロパティに付与された属性をまとめて取得するには、GetCustomAttributes を使います。

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

if (property != null)
{
IEnumerable<Attribute> attributes = property.GetCustomAttributes();

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

特定の属性が付いているかだけを確認したい場合は、IsDefined も使えます。

C#
bool hasRequired = property?.IsDefined(typeof(RequiredAttribute), inherit: true) == true;

Console.WriteLine(hasRequired);

属性とReflectionを組み合わせると、プロパティごとに表示名、検証ルール、マッピング名、出力対象などのメタデータを持たせられます。

8-2. DisplayName属性を取得する例

DisplayNameAttribute を使うと、プロパティに表示名を付けられます。

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

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

属性を読み取る例です。

C#
foreach (PropertyInfo property in typeof(Product).GetProperties())
{
DisplayNameAttribute? displayName =
property.GetCustomAttribute<DisplayNameAttribute>();

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

Console.WriteLine(label);
}

CSVヘッダーや画面表示用ラベルを動的に生成したい場合に便利です。

8-3. Required属性やValidation属性を判定する例

System.ComponentModel.DataAnnotations の属性を使うと、プロパティに検証ルールを付けられます。

C#
public class RegisterUser
{
[Required]
[StringLength(20)]
public string Name { get; set; } = "";
}

RequiredAttribute が付いているか判定する例です。

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

bool required = property?.GetCustomAttribute<RequiredAttribute>() != null;

Console.WriteLine(required);

StringLengthAttribute を取得する例です。

C#
StringLengthAttribute? stringLength =
property?.GetCustomAttribute<StringLengthAttribute>();

if (stringLength != null)
{
Console.WriteLine(stringLength.MaximumLength);
}

属性を使うと、プロパティごとの入力チェックやエラーメッセージ生成を動的に行えます。

8-4. 独自属性を定義してPropertyInfoで読み取る

独自属性を定義することもできます。

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

public int Order { get; }

public CsvColumnAttribute(string name, int order)
{
Name = name;
Order = order;
}
}

プロパティに付与します。

C#
public class ExportUser
{
[CsvColumn("ID", 1)]
public int Id { get; set; }

[CsvColumn("ユーザー名", 2)]
public string Name { get; set; } = "";
}

読み取る例です。

C#
foreach (PropertyInfo property in typeof(ExportUser).GetProperties())
{
CsvColumnAttribute? attribute =
property.GetCustomAttribute<CsvColumnAttribute>();

if (attribute != null)
{
Console.WriteLine($"{property.Name}: {attribute.Name}, {attribute.Order}");
}
}

独自属性を使えば、プロパティに対してアプリケーション固有のメタデータを自然に付与できます。

8-5. 属性を使った動的マッピングの実装例

属性を使って、プロパティ名とは異なるキー名でDictionaryへ変換する例です。

C#
public static Dictionary<string, object?> ToMappedDictionary(object obj)
{
if (obj == null)
{
throw new ArgumentNullException(nameof(obj));
}

var result = new Dictionary<string, object?>();

PropertyInfo[] properties = obj.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance);

foreach (PropertyInfo property in properties)
{
if (!property.CanRead || property.GetIndexParameters().Length > 0)
{
continue;
}

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

string key = attribute?.Name ?? property.Name;

result[key] = property.GetValue(obj);
}

return result;
}

使用例です。

C#
var user = new ExportUser
{
Id = 1,
Name = "Taro"
};

Dictionary<string, object?> dictionary = ToMappedDictionary(user);

foreach (var item in dictionary)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}

属性を組み合わせることで、プロパティ名と外部形式の項目名を分離できます。CSV、Excel、JSON、固定長ファイル、画面表示など、さまざまなマッピング処理に応用できます。

9. PropertyInfoを使うときの注意点

9-1. Reflectionは通常のアクセスより遅い

Reflectionは柔軟ですが、通常のプロパティアクセスより遅くなる傾向があります。

通常のアクセスは次のようにシンプルです。

C#
string name = user.Name;

Reflectionでは、型情報の取得、プロパティ検索、メソッド呼び出しが実行時に行われます。

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

数回の呼び出しであれば問題にならないことが多いですが、大量データ処理や高頻度で呼ばれる処理ではパフォーマンスに影響する可能性があります。

9-2. パフォーマンスを改善するキャッシュ戦略

GetPropertyGetProperties を毎回呼ぶと、同じ型に対して何度もメタデータ検索が発生します。改善策として、取得した PropertyInfo をキャッシュします。

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

public static PropertyInfo[] GetCachedProperties(Type type)
{
if (PropertyCache.TryGetValue(type, out PropertyInfo[]? properties))
{
return properties;
}

properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
PropertyCache[type] = properties;

return properties;
}

マルチスレッド環境では、ConcurrentDictionary を使うと安全です。

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

public static PropertyInfo[] GetCachedProperties(Type type)
{
return PropertyCache.GetOrAdd(
type,
t => t.GetProperties(BindingFlags.Public | BindingFlags.Instance));
}

さらに高速化したい場合は、Expression Treeやデリゲートを生成してキャッシュする方法もあります。ただし実装が複雑になるため、まずは PropertyInfo のキャッシュから検討するとよいでしょう。

9-3. privateメンバー操作の注意点

BindingFlags.NonPublic を使うとprivateプロパティを取得できますが、安易に使うべきではありません。

privateメンバーは本来、クラス内部の実装詳細です。外部コードからprivateプロパティに依存すると、次のような問題が起きやすくなります。

・クラス内部の変更で呼び出し側が壊れる
・カプセル化が崩れる
・不正な状態を作れる
・セキュリティや実行環境の制限に引っかかる
・AOTやトリミング環境で動作が不安定になる可能性がある

テスト、移行ツール、デバッグ補助など限定的な用途にとどめるのが安全です。

9-4. 型安全性が低くなる問題

PropertyInfo.GetValue の戻り値は object? です。そのため、キャストミスが実行時まで分からない場合があります。

C#
object? value = property.GetValue(user);
int name = (int)value!; // 実行時エラーの可能性

通常のコードなら、コンパイル時に型エラーを検出できます。

C#
// int name = user.Name; // コンパイルエラー

Reflectionを使う場合は、PropertyType を確認してからキャストする、ジェネリックメソッドにする、変換処理を明示するなどの対策が必要です。

C#
if (property.PropertyType == typeof(string))
{
string? text = property.GetValue(user) as string;
}

9-5. リファクタリング時に壊れやすいポイント

Reflectionでは、プロパティ名を文字列で指定することが多くなります。

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

この場合、Name プロパティを FullName にリネームしても、文字列 "Name" は自動で変更されないことがあります。その結果、実行時に GetPropertynull を返します。

対策として、可能な限り nameof を使いましょう。

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

nameof を使うと、リファクタリング時にIDEが追従しやすくなります。

9-6. AOT・トリミング環境での注意点

.NETのAOTやトリミングを使う環境では、Reflectionに注意が必要です。トリミングでは未使用と判断されたメンバーが削除される可能性があり、複雑なReflectionパターンは静的解析で追跡しにくい場合があります。Microsoft Learnでも、トリミングを有効にすると.NET SDKが互換性のないコードパターンを検出するための静的解析を行い、Reflectionのようなパターンで警告が出ることがあると説明されています。Microsoft Learn

特に、文字列でプロパティ名を指定するコードや、外部入力をもとに型やメンバーを検索するコードは、ビルド時に必要なメンバーを判断しにくくなります。

C#
PropertyInfo? property = type.GetProperty(propertyName);

対策としては、次のような方法があります。

・Reflection対象を限定する
・DynamicallyAccessedMembers属性を検討する
・ソースジェネレーターを使う
・AOT対応のシリアライザーを使う
・トリミング警告を無視せず確認する

Reflectionベースのシリアライザーはトリミングと相性が悪いケースがあり、Microsoft Learnでは代替としてソース生成を使う選択肢も示されています。Microsoft Learn

10. PropertyInfoでよくあるエラーと解決方法

10-1. GetPropertyがnullを返す原因

GetPropertynull を返す主な原因は、指定した条件に一致するプロパティが存在しないことです。

よくある原因は次の通りです。

・プロパティ名のスペルミス
・大文字小文字の違い
・public以外のプロパティをBindingFlagsなしで取得しようとしている
・staticプロパティにInstanceを指定している
・インスタンスプロパティにStaticを指定している
・継承関係や隠蔽の影響を受けている

privateプロパティを取得したい場合は、次のようにします。

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

staticプロパティを取得したい場合は、次のようにします。

C#
PropertyInfo? property = typeof(User).GetProperty(
nameof(User.AppName),
BindingFlags.Public | BindingFlags.Static);

大文字・小文字を無視したい場合は、BindingFlags.IgnoreCase を追加します。

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

10-2. AmbiguousMatchExceptionが発生する原因

AmbiguousMatchException は、指定条件に一致するメンバーが複数見つかった場合に発生します。Type.GetProperty のドキュメントでも、指定した名前や引数型に一致するプロパティが複数ある場合に AmbiguousMatchException が発生すると説明されています。Microsoft Learn

たとえば、indexerやオーバーロードに近い構造、継承階層での同名プロパティ、戻り値型違いの検索などで発生することがあります。

対処法としては、より具体的なオーバーロードを使います。

C#
PropertyInfo? property = typeof(Sample).GetProperty(
"Item",
BindingFlags.Public | BindingFlags.Instance,
binder: null,
returnType: typeof(string),
types: new[] { typeof(int) },
modifiers: null);

単純なプロパティ取得でも、同名の候補があり得る設計では、GetProperties で候補を取得して条件を絞り込む方法が有効です。

C#
PropertyInfo? property = typeof(Sample)
.GetProperties()
.FirstOrDefault(p =>
p.Name == "Name" &&
p.PropertyType == typeof(string));

10-3. TargetExceptionが発生する原因

TargetException は、GetValueSetValue に渡した対象オブジェクトが不正な場合に発生することがあります。

例として、UserPropertyInfo に対して別の型のオブジェクトを渡すケースがあります。

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

object target = new object();

// property?.GetValue(target); // 対象が不正

対処法は、PropertyInfo.DeclaringTypeIsAssignableFrom を使って対象オブジェクトが正しい型か確認することです。

C#
if (property?.DeclaringType?.IsAssignableFrom(target.GetType()) == true)
{
object? value = property.GetValue(target);
}

staticプロパティでは対象オブジェクトに null を渡します。

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

10-4. ArgumentExceptionが発生する原因

ArgumentException は、SetValue に渡した値の型がプロパティ型に合わない場合によく発生します。

C#
var user = new User();

PropertyInfo? property = typeof(User).GetProperty(nameof(User.Age));

// property?.SetValue(user, "30"); // stringをint?に直接設定できない

対処法は、事前に型変換することです。

C#
object? convertedValue = ConvertValue("30", property!.PropertyType);
property.SetValue(user, convertedValue);

また、indexerに対してインデックス引数を渡していない場合や、インデックス引数の型が違う場合にもエラーになります。

C#
PropertyInfo? indexer = typeof(User).GetProperty("Item");

object? value = indexer?.GetValue(user, new object[] { 1 });

10-5. TargetInvocationExceptionが発生する原因

TargetInvocationException は、Reflectionで呼び出したgetterやsetterの内部で例外が発生した場合に、その例外をラップして発生することがあります。

C#
public class ErrorUser
{
public string Name
{
get => throw new InvalidOperationException("getterでエラー");
}
}
C#
try
{
var user = new ErrorUser();
PropertyInfo? property = typeof(ErrorUser).GetProperty(nameof(ErrorUser.Name));
object? value = property?.GetValue(user);
}
catch (TargetInvocationException ex)
{
Console.WriteLine(ex.InnerException?.Message);
}

実際の原因は InnerException に入っていることが多いため、ログには InnerException も出力しましょう。

C#
catch (TargetInvocationException ex)
{
Console.WriteLine(ex.InnerException?.GetType().Name);
Console.WriteLine(ex.InnerException?.Message);
}

10-6. privateプロパティにアクセスできない場合の対処法

privateプロパティにアクセスできない場合は、まず BindingFlags を確認します。

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

private staticプロパティの場合は、Static を指定します。

C#
PropertyInfo? property = typeof(Sample).GetProperty(
"Secret",
BindingFlags.NonPublic | BindingFlags.Static);

それでもアクセスできない場合は、次の点を確認します。

・プロパティ名が正しいか
・対象がフィールドではなくプロパティか
・基底クラス側に定義されていないか
・実行環境で非publicメンバーへのアクセスが制限されていないか
・AOTやトリミングの影響を受けていないか

privateメンバーへのアクセスは、できるかどうかだけでなく、設計として妥当かも確認することが重要です。

11. PropertyInfoのベストプラクティス

11-1. 文字列のプロパティ名を安全に扱う

PropertyInfo ではプロパティ名を文字列で扱うことが多いため、タイポに注意が必要です。

悪い例です。

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

このコードはコンパイルできますが、実行時に null になります。

安全な例です。

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

ユーザー入力や外部ファイルからプロパティ名を受け取る場合は、許可リストを用意するのも有効です。

C#
private static readonly HashSet<string> AllowedProperties = new()
{
nameof(User.Id),
nameof(User.Name),
nameof(User.Age)
};
C#
if (!AllowedProperties.Contains(propertyName))
{
throw new InvalidOperationException("許可されていないプロパティです。");
}

11-2. nameofを使って保守性を高める

nameof は、シンボル名を文字列として取得する演算子です。

C#
string propertyName = nameof(User.Name);

nameof を使うと、プロパティ名をリネームしたときにIDEのリファクタリング機能が追従しやすくなります。

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

属性やマッピング定義でも、可能な限り nameof を使うと保守性が上がります。

C#
var map = new Dictionary<string, string>
{
[nameof(User.Name)] = "user_name",
[nameof(User.Age)] = "age"
};

11-3. CanRead・CanWriteを事前に確認する

値を取得する前には CanRead、設定する前には CanWrite を確認しましょう。

C#
if (property != null && property.CanRead)
{
object? value = property.GetValue(user);
}
C#
if (property != null && property.CanWrite)
{
property.SetValue(user, "Taro");
}

indexerも除外する場合は、GetIndexParameters() を確認します。

C#
if (property.GetIndexParameters().Length > 0)
{
continue;
}

汎用処理では、読み書き可否とindexer判定を入れておくと例外を減らせます。

11-4. 取得したPropertyInfoをキャッシュする

同じ型に対して何度もReflectionを行う場合は、PropertyInfo をキャッシュしましょう。

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

public static PropertyInfo[] GetPropertiesCached(Type type)
{
return Cache.GetOrAdd(
type,
t => t.GetProperties(BindingFlags.Public | BindingFlags.Instance));
}

プロパティ名で取得する場合は、Dictionary化してキャッシュすると検索が速くなります。

C#
private static readonly ConcurrentDictionary<Type, Dictionary<string, PropertyInfo>> PropertyMapCache = new();

public static PropertyInfo? GetPropertyCached(Type type, string name)
{
Dictionary<string, PropertyInfo> map = PropertyMapCache.GetOrAdd(
type,
t => t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToDictionary(p => p.Name));

return map.TryGetValue(name, out PropertyInfo? property)
? property
: null;
}

キャッシュはパフォーマンス改善に有効ですが、メモリ使用量とのバランスも考える必要があります。

11-5. 通常の実装とReflectionを使い分ける

Reflectionは便利ですが、通常のコードで書ける処理まで無理にReflection化する必要はありません。

通常のプロパティアクセスで十分な例です。

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

Reflectionを使う価値がある例です。

・型が実行時まで分からない
・複数の型を汎用的に処理したい
・属性を読み取って動的に処理したい
・フレームワークやライブラリの共通処理を作りたい
・ログ、CSV、マッピング、テスト補助を汎用化したい

業務ロジックの中心部分では型安全な通常コードを使い、汎用化が必要な境界部分でReflectionを使うのがおすすめです。

11-6. 例外処理を考慮した実装にする

Reflectionは実行時に失敗する可能性があるため、例外処理を考慮した実装にしましょう。

C#
public static bool TrySetValueSafe(
object target,
string propertyName,
object? value,
out string? errorMessage)
{
errorMessage = null;

try
{
PropertyInfo? property = target.GetType().GetProperty(propertyName);

if (property == null)
{
errorMessage = "プロパティが見つかりません。";
return false;
}

if (!property.CanWrite)
{
errorMessage = "プロパティに書き込めません。";
return false;
}

object? convertedValue = ConvertValue(value, property.PropertyType);
property.SetValue(target, convertedValue);

return true;
}
catch (Exception ex)
{
errorMessage = ex.Message;
return false;
}
}

呼び出し側です。

C#
if (!TrySetValueSafe(user, nameof(User.Age), "30", out string? error))
{
Console.WriteLine(error);
}

ライブラリや共通部品では、失敗理由を呼び出し元に返せる設計にしておくと使いやすくなります。

12. C# PropertyInfoに関するよくある質問

12-1. PropertyInfoでprivateプロパティは取得できる?

取得できます。ただし、BindingFlags.NonPublicBindingFlags.Instance または BindingFlags.Static を指定する必要があります。

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

privateプロパティを含めて一覧取得する場合は、次のようにします。

C#
PropertyInfo[] properties = typeof(User).GetProperties(
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance);

ただし、privateメンバーへの依存は保守性を下げるため、通常の業務ロジックでは避けるのが基本です。

12-2. PropertyInfoで読み取り専用プロパティは変更できる?

通常は変更できません。getterのみのプロパティは CanWritefalse になります。

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

if (property?.CanWrite == true)
{
property.SetValue(obj, "NewName");
}

バッキングフィールドをReflectionで書き換える方法はありますが、実装依存で壊れやすいため推奨されません。

12-3. PropertyInfoでプロパティの型を判定するには?

PropertyType を使います。

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

if (property?.PropertyType == typeof(string))
{
Console.WriteLine("string型です。");
}

Nullable型の場合は、Nullable.GetUnderlyingType を使います。

C#
Type? underlyingType = Nullable.GetUnderlyingType(property.PropertyType);

if (underlyingType != null)
{
Console.WriteLine($"Nullable<{underlyingType.Name}>です。");
}

12-4. PropertyInfoで属性を取得するには?

GetCustomAttribute<T> または GetCustomAttributes を使います。

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

DisplayNameAttribute? displayName =
property?.GetCustomAttribute<DisplayNameAttribute>();

Console.WriteLine(displayName?.DisplayName);

複数の属性を取得する場合は、次のようにします。

C#
foreach (Attribute attribute in property.GetCustomAttributes())
{
Console.WriteLine(attribute.GetType().Name);
}

12-5. GetValueとSetValueの違いは?

GetValue はプロパティの値を読み取るメソッドです。

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

SetValue はプロパティの値を設定するメソッドです。

C#
property.SetValue(user, "Taro");

読み取り前には CanRead、書き込み前には CanWrite を確認すると安全です。

12-6. PropertyInfoはどんな場面で使うべき?

PropertyInfo は、実行時にプロパティ情報を動的に扱いたい場合に使います。

代表的な用途は次の通りです。

・オブジェクトのプロパティ一覧表示
・DTOやViewModelの値コピー
・Dictionary変換
・CSV出力
・ログ出力
・属性ベースのマッピング
・バリデーション
・テスト補助
・フレームワークやライブラリの共通処理

逆に、対象の型やプロパティがコンパイル時に分かっている場合は、通常のプロパティアクセスを使う方がシンプルで安全です。

12-7. Reflectionを使わない代替手段はある?

あります。用途によって、次のような代替手段を検討できます。

・通常のプロパティアクセス
・インターフェース
・ジェネリック
・Expression Tree
・デリゲート
・ソースジェネレーター
・AutoMapperなどのマッピングライブラリ
・System.Text.Jsonのソース生成

たとえば、DTOのコピーであれば明示的に書く方法が最も型安全です。

C#
var viewModel = new UserViewModel
{
Id = dto.Id,
Name = dto.Name
};

大量のマッピングやAOT対応が必要な場合は、Reflectionよりソースジェネレーターの方が適していることがあります。Reflectionは柔軟性が必要な場所に絞って使うのがよいでしょう。

まとめ

C#の PropertyInfo は、Reflectionを使ってプロパティ情報を実行時に扱うための重要なクラスです。Type.GetProperty で特定のプロパティを取得し、GetProperties で一覧を取得し、GetValue で値を読み取り、SetValue で値を書き換えられます。Type.GetProperty にはpublicプロパティを検索する基本的なオーバーロードや、BindingFlags を指定して検索条件を制御するオーバーロードがあります。Microsoft Learn

基本的な流れは次の通りです。

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

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

if (property != null && property.CanRead)
{
object? value = property.GetValue(user);
Console.WriteLine(value);
}

値を設定する場合は、次のようにします。

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

PropertyInfo は、DTOコピー、Dictionary変換、CSV出力、ログ出力、属性ベースのマッピング、バリデーション、テストコードなど、多くの場面で役立ちます。一方で、Reflectionは通常のプロパティアクセスより遅く、型安全性も低くなりやすいため、使いどころを見極めることが大切です。

実務では、次のポイントを意識しましょう。

・プロパティ名には可能な限りnameofを使う
・GetPropertyがnullを返す可能性を考慮する
・CanReadとCanWriteを確認する
・indexerは必要に応じて除外する
・PropertyInfoをキャッシュしてパフォーマンスを改善する
・privateメンバーへのアクセスは最小限にする
・AOTやトリミング環境ではReflectionの使い方に注意する

PropertyInfo を正しく使えるようになると、C#で汎用的な処理やメタデータ駆動の実装がしやすくなります。通常の型安全なコードとReflectionを適切に使い分けながら、保守性と柔軟性のバランスを取ることが重要です。