初めに
この記事で使用している.NET
のバージョンは8
です。
C#初心者が調べたことをまとめているため、不備・不足あるかもしれませんがご了承ください。
改善したいコード
プロパティに付与した属性の値を取得したいときは、以下のようにリフレクションを用いて「プロパティ名」を文字列で指定して得るというのが一つ手段としてあるかなと思います。
public class LogicalNameAttribute(string value) : Attribute
{
public string Value { get; } = value;
}
public class Person
{
[LogicalName("名前")]
public string? Name { get; set; }
[LogicalName("年齢")]
public int? Age { get; set; }
}
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とかだとよしなに変更を追従してくれますが、文字列で指定していたら修正漏れが発生するかもしれません。
改善コード案1
上記のことから、調べたりした結果の改善案が以下です(記載のないクラスは上記と同様)。
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クラスの構成要素だけを用いて属性の値を得ることができました。
補完が効きますから、誤った文字列を指定してしまった結果のエラーとかは回避できるのではないでしょうか。
もちろん、ラムダ式で書く必要があるという意味では、無駄が発生しているデメリットはあるかなと思います。
改善コード案2
「改善コード案1」では、GetValueクラスにPerson
クラスを指定していましたが、もっと汎用的に使いたい場面があると思います。
それに対応した改善案について調べた結果が以下のコードです。
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
を取り出す処理が増えています。
改善コード案3
コメントで頂いた案です。
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}");
}
}
「改善したいコード」を改善するならばこれで問題ありませんね。