C# - プロパティに付与した属性の値を取得したいときにプロパティ名を文字列で指定したくない

# 初めに
この記事で使用している`.NET`のバージョンは`8`です。

C#初心者が調べたことをまとめているため、不備・不足あるかもしれませんがご了承ください。

# 改善したいコード
プロパティに付与した属性の値を取得したいときは、以下のようにリフレクションを用いて「プロパティ名」を文字列で指定して得るというのが一つ手段としてあるかなと思います。

```csharp
public class LogicalNameAttribute(string value) : Attribute
{
    public string Value { get; } = value;
}
```
```csharp
public class Person
{
    [LogicalName("名前")]
    public string? Name { get; set; }

    [LogicalName("年齢")]
    public int? Age { get; set; }
}
```
```csharp
using System.Reflection;
public class Program
{
    public static void Main(string[] args)
    {
        var nameP = typeof(Person).GetProperty("Name");
        var nameLN = nameP?.GetCustomAttribute<LogicalNameAttribute>()?.Value;
        var ageP = typeof(Person).GetProperty("Age");
        var ageLN = ageP?.GetCustomAttribute<LogicalNameAttribute>()?.Value;

        Console.WriteLine($"Name : {nameLN}, Age : {ageLN}");
    }
}
```
↓出力結果
```
Name : 名前, Age : 年齢
```

ただ、せっかくPersonクラスでプロパティとして定義しているので、文字列を指定するのではなくプロパティ自体をEnumのような感じで指定できればいいなと考えました。
また、プロパティ名を変更するとしたとき、Visual Studioとかだとよしなに変更を追従してくれますが、文字列で指定していたら修正漏れが発生するかもしれません。

# 改善コード案１
上記のことから、調べたりした結果の改善案が以下です（記載のないクラスは上記と同様）。
```csharp
using System.Linq.Expressions;
using System.Reflection;
public class Program
{
    public static void Main(string[] args)
    {
        var nameLN = GetValue(x => x.Name);
        var ageLN = GetValue(x => x.Age);
        Console.WriteLine($"Name : {nameLN}, Age : {ageLN}");
    }

    public static string? GetValue<T>(Expression<Func<Person, T>> expression)
    {
        var memberExpression = expression?.Body as MemberExpression;
        var property = memberExpression?.Member as PropertyInfo;
        return property?.GetCustomAttribute<LogicalNameAttribute>()?.Value;
    }
}
```
式ツリーを引数の型とした`GetValue`メソッドを新たに作成しました。例のようにプロパティを呼び出すラムダ式を渡して使用します。
「プロパティ名の文字列」を指定せずに、Personクラスの構成要素だけを用いて属性の値を得ることができました。
補完が効きますから、誤った文字列を指定してしまった結果のエラーとかは回避できるのではないでしょうか。

もちろん、ラムダ式で書く必要があるという意味では、無駄が発生しているデメリットはあるかなと思います。

# 改善コード案２
「改善コード案１」では、GetValueクラスに`Person`クラスを指定していましたが、もっと汎用的に使いたい場面があると思います。
それに対応した改善案について調べた結果が以下のコードです。
```csharp
public class Program
{
    public static void Main(string[] args)
    {
        var nameLN = GetValue<Person>(x => x.Name);
        var ageLN = GetValue<Person>(x => x.Age);
        Console.WriteLine($"Name : {nameLN}, Age : {ageLN}");
    }

    public static string? GetValue<T>(Expression<Func<T, object?>> expression)
    {
        var memberExpression = expression?.Body as MemberExpression;
        if (memberExpression == null && expression?.Body is UnaryExpression unaryExpression)
        {
            memberExpression = unaryExpression?.Operand as MemberExpression;
        }
        var property = memberExpression?.Member as PropertyInfo;
        return property?.GetCustomAttribute<LogicalNameAttribute>()?.Value;
    }
}
```
`GetValue`メソッドを改修しました。

処理中に`UnaryExpression`が登場していますが、プロパティの型が値型(`int`とか？)だと、ボックス化の関係で`UnaryExpression`に変換されるらしいです。そのため、`UnaryExpression`から`MemberExpression`を取り出す処理が増えています。

# 改善コード案３
コメントで頂いた案です。
nameofの中では「クラス名．プロパティ名」で指定できるようです。
以下が改善コード案です。
```
using System.Reflection;
public class Program
{
    public static void Main(string[] args)
    {
        var nameP = typeof(Person).GetProperty(nameof(Person.Name));
        var nameLN = nameP?.GetCustomAttribute<LogicalNameAttribute>()?.Value;
        var ageP = typeof(Person).GetProperty(nameof(Person.Age));
        var ageLN = ageP?.GetCustomAttribute<LogicalNameAttribute>()?.Value;

        Console.WriteLine($"Name : {nameLN}, Age : {ageLN}");
    }
}
```
「改善したいコード」を改善するならばこれで問題ありませんね。

# 参考
- [C# - way to pass property of the generic type like an enum - Stack Overflow ](https://stackoverflow.com/questions/73068065/c-sharp-way-to-pass-property-of-the-generic-type-like-an-enum)
- [c# - Expression for Type members results in different Expressions (MemberExpression, UnaryExpression) - Stack Overflow ](https://stackoverflow.com/questions/12975373/expression-for-type-members-results-in-different-expressions-memberexpression)