C# クラス内のプロパティへのアクセス方法

概要

C#のプロパティへのアクセス方法で少し混乱したのでまとめ。

前提

以下のようにクラスを定義しているとします。
Week(またはWeek2)クラスにはMondayというプロパティとDayというプロパティがあり、さらにDay(またはDay2)クラスにはMorningというプロパティとAfterNoonというプロパティがあります。

 public class Week
    {
        public string Monday { get; set; } = "MON";
        public Day Day { get; set; } = new Day();

    }
    public class Week2
    {
        public string Monday { get; set; } = "月曜日";
        public Day2 Day { get; set; } = new Day2();

    }
    public class Day
    {
        public string Morning { get; set; } = "AM";
        public string AfterNoon { get; set; } = "PM";
    }
    public class Day2
    {
        public string Morning { get; set; } = "午前";
        public string AfterNoon { get; set; } = "午後";
    }

【1】プロパティへのアクセス方法 その1 (直観的)

⇒「クラスのインスタンス.プロパティ名」

Week week = new Week();
Console.WriteLine($"week.Monday={week.Monday}");
Console.WriteLine($"week.Day.AfterNoon={week.Day.AfterNoon}");

【2-1】プロパティへのアクセス方法 その2 (リフレクション機能を利用)

⇒「クラスのインスタンス.GetType().GetProperty(プロパティ名).GetValue(クラスのインスタンス)」

Console.WriteLine($"week.Monday={week.GetType().GetProperty("Monday").GetValue(week)}");
Console.WriteLine($"week.Day.AfterNoon={week.GetType().GetProperty("Day").GetValue(week).GetType().GetProperty("Morning").GetValue(week.GetType().GetProperty("Day").GetValue(week)).ToString()}");

【2-2】リフレクション機能を用いたプロパティへのアクセス方法 順を追ってみる

①実行時型情報を手に入れる

⇒「object型.GetType()」

Type type = week.GetType();
Console.WriteLine($"type={type}");    // => 「type=ConsoleApp1.Week

②プロパティの有無を確認する

⇒「object型.GetType().GetProperty(プロパティ名)」

PropertyInfo propertyInfo = week.GetType().GetProperty("Monday");

プロパティが存在しない場合、戻り値(PropertyInfo型の変数 propertyInfo) は nullとなります。

③-1)プロパティに値を設定する

⇒「PropertyInfo.SetValue(プロパティが含まれるクラスのインスタンス, 設定したい値)」

propertyInfo.SetValue(week, "月曜日");

・備考:②で言及したように、propertyInfoにはnullが入る可能性があるため、実践で使う場合は、? を用いて、propertyInfo?.SetValue("aaa")とすると、Exceptionの発生を防ぐことができるのでおすすめです。
演算子「?」については Null 条件演算子 - C# リファレンス | Microsoft Docsを参照ください。
簡単に言うと、nullのオブジェクトに対してプロパティを取得しに行っても、nullPoiintException例外を起こさずにnullを返してくれるという機能を持っています。

③-2)プロパティから値を取得する

⇒「ProertyInfo.GetValue(プロパティが含まれるクラスのインスタンス)」

propertyInfo.GetValue(week);

・③-1同様に、propertyInfo?.GetValue("aaa")にするのがおすすめです。

【3】クラスのプロパティをループで回して処理したい場合

foreach (PropertyInfo item in week.GetType().GetProperties())
{
    Console.WriteLine("============");
    Console.WriteLine(item.Name);
    Console.WriteLine(item.GetValue(week));
}

foreach(var item in week.GetType().GetProperties()) という書き方もできます。

【4】参考 dynamic型

dynamicは実行時に初めてプロパティのチェックを行うため、dynamic型のオブジェクト.プロパティ名と書いてもコンパイルエラーになりません。
詳細は省略しますが、【5】のソース内のRefrectionTest3メソッドとRefrectionTest2メソッドの「//プロパティ要素に直接アクセス」の部分を比べてもらえれば雰囲気がつかめるかと思います。

【5】参考ソース

using System;
using System.Reflection;

namespace ConsoleApp1
{
    public class Week
    {
        public string Monday { get; set; } = "MON";
        public Day Day { get; set; } = new Day();

    }
    public class Week2
    {
        public string Monday { get; set; } = "月曜日";
        public Day2 Day { get; set; } = new Day2();

    }
    public class Day
    {
        public string Morning { get; set; } = "AM";
        public string AfterNoon { get; set; } = "PM";
    }
    public class Day2
    {
        public string Morning { get; set; } = "午前";
        public string AfterNoon { get; set; } = "午後";
    }

    class Program
    {
        static void Main(string[] args)
        {

            // 実践例1
            Console.WriteLine($"result 1 = {RefrectionTest1(week)}");

            // 実践例2 Week型とは違う「Week2」型だが、object型で引数を受け付けているため、問題なく処理ができている
            Week2 week2 = new Week2();
            Console.WriteLine($"result 2-1 = {RefrectionTest2(week)}");
            Console.WriteLine($"result 2-2 = {RefrectionTest2(week2)}");

            // 実践例3  これもWeek型、Week2型いずれが来ても問題ない。(Week,Week2の両方ともDayプロパティ および Day内のMorningプロパティを持っているため。
            //(プロパティがないと、実行時に例外発生))
            Console.WriteLine($"result 3-1 = {RefrectionTest3(week)}");
            Console.WriteLine($"result 3-2 = {RefrectionTest3(week2)}");

            Console.ReadKey();
        }

        // 引数が(Week obj)となっており、Week型を使うことを明確に決めているため、以下の2通りの両方を用いることができる。
        public static string RefrectionTest1(Week obj)
        {
            Console.WriteLine("=====RefrectionTest1=====");

            //リフレクション機能を使用
            Console.WriteLine(obj.GetType().GetProperty("Day").GetValue(obj));
            Console.WriteLine(obj.GetType().GetProperty("Day").GetValue(obj).GetType().ToString());
            var day1_1 = obj.GetType().GetProperty("Day").GetValue(obj);
            Console.WriteLine(day1_1.GetType().GetProperty("Morning").GetValue(day1_1).ToString());

            //プロパティ要素に直接アクセス
            Console.WriteLine(obj.Day);
            Console.WriteLine(obj.Day.Morning);

            return obj.Day.Morning;
        }

        // 引数が(object obj)となっており、objにDayというプロパティはないためコンパイルエラーとなる。
        // なお、上述 実践例2の通り、ReferectionTest2Week型の引数weekを渡した場合も問題なく実行できる。
        public static string RefrectionTest2(object obj)
        {
            Console.WriteLine("=====RefrectionTest2=====");

            //リフレクション機能を使用
            Console.WriteLine(obj.GetType().GetProperty("Day").GetValue(obj));
            Console.WriteLine(obj.GetType().GetProperty("Day").GetValue(obj).GetType().ToString());
            var day2_1 = obj.GetType().GetProperty("Day").GetValue(obj); 
            Console.WriteLine(day2_1.GetType().GetProperty("Morning").GetValue(day2_1).ToString());

            //プロパティ要素に直接アクセス
            /* 以下コメントアウト部分は、「'object''Day'の定義が含まれておらず、型'object'の最初の引数を受け付けるアクセス可能な拡張メソッド'Day'が見つかりませんでした。
             usingディレクティブまたはアセンブリ参照が不足していないかを確認してください。」 というエラー発生 */
            //Console.WriteLine(obj.Day);
            //Console.WriteLine(obj.Day.Morning);

            return day2_1.GetType().GetProperty("Morning").GetValue(day2_1).ToString();
        }

        // dynamicは実行時に初めてプロパティのチェックを行うため、コンパイル時点ではdynの中にDayプロパティがないとは言えず、dyn.Dayと書いてもコンパイルエラーにならない。
        public static string RefrectionTest3(dynamic dyn)
        {
            Console.WriteLine("=====RefrectionTest3=====");

            //リフレクション機能を使用
            Console.WriteLine(dyn.GetType().GetProperty("Day").GetValue(dyn));
            Console.WriteLine(dyn.GetType().GetProperty("Day").GetValue(dyn).GetType().ToString());
            var day3_1 = dyn.GetType().GetProperty("Day").GetValue(dyn);
            Console.WriteLine(day3_1.GetType().GetProperty("Morning").GetValue(day3_1).ToString());

            //プロパティ要素に直接アクセス
            Console.WriteLine(dyn.Day);
            Console.WriteLine(dyn.Day.Morning);

            return dyn.Day.Morning;
        }
    }
}

参考サイト

メソッドやプロパティの有無を確認して呼び出すには?:.NET TIPS - @IT

実行時型情報 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C

[C#] 文字列でプロパティ名を指定してアクセス(参照・更新)する方法 │ Web備忘録

そのほか

改行がうまくできなくて読みづらいですね…。
```csp
ソースコード
```
という書き方をすると、よい感じの色付けはなされるみたいです!
リアルタイムプレビューだと読みやすく変な改行も入らずなんですけど、実際の画面だと表示が崩れてしまって読みにくいですね…。