初めに
この記事で使用している.NET
のバージョンは8
です。
C#初心者が調べたことをまとめているため、不備・不足あるかもしれませんがご了承ください。
改善したいコード
TargetClass
クラスのRegister
メソッドのlogicalName
には現在任意の文字列を指定できる状態で、外部からプロパティに付与した属性の値を与えている状態です。
ここでやりたいこととしては、引数に指定するのをPerson
クラスのプロパティに制限したいなと思っています。
また、その拡張として、Person
クラスに限らないクラスが指定できるようにすることです。
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; }
}
// ★改善したいコード
public class TargetClass
{
private List<string> _list = [];
public void Register(string? logicalName, object? value)
{
ArgumentNullException.ThrowIfNull(logicalName);
ArgumentNullException.ThrowIfNull(value);
_list.Add($"{logicalName} : {value}");
}
public string Output() => string.Join(", ", _list);
}
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;
var target = new TargetClass();
target.Register(nameLN, "john");
target.Register(ageLN, 21);
Console.WriteLine(target.Output());
}
}
↓出力結果
名前 : john, 年齢 : 21
改善コード案1
TargerClass
クラスにジェネリクスを導入してみました。使用例(Program
クラス)ではPersonクラスを指定しています。
Register
メソッドにプロパティ名を指定することで、Personクラスにその名をもつプロパティがあれば、属性から値を取得してくれる処理になってます。
public class Program
{
public static void Main(string[] args)
{
var target = new TargetClass<Person>();
target.Register(nameof(Person.Name), "john");
target.Register(nameof(Person.Age), 21);
Console.WriteLine(target.Output());
}
}
public class TargetClass<T>
{
private List<string> _list = [];
public void Register(string? propertyName, object? value)
{
ArgumentNullException.ThrowIfNull(propertyName);
ArgumentNullException.ThrowIfNull(value);
var property = typeof(T).GetProperty(propertyName);
var logicalName = (property?.GetCustomAttribute<LogicalNameAttribute>()?.Value) ?? throw new Exception();
_list.Add($"{logicalName} : {value}");
}
public string Output() => string.Join(", ", _list);
}
プロパティ名として、「nameof(Person.Name)」のように指定しているものの、引数の型としてはstring
なので、好きに文字列を与えることができてしまいます。
どうにか、Register
メソッドに与える時点で、Personクラスに制限することはできないでしょうか?
改善コード案2
Person.Name
のように指定する方法はわからなかったものの、「プロパティを呼ぶラムダ式」を指定することで疑似的にプロパティを指定する改善案です。
public class Program
{
public static void Main(string[] args)
{
var target = new TargetClass<Person>();
target.Register(x => x.Name, "john");
target.Register(x => x.Age, 21);
Console.WriteLine(target.Output());
}
}
public class TargetClass<T>
{
private List<string> _list = [];
public void Register<U>(Expression<Func<T, U>> expression, U value)
{
ArgumentNullException.ThrowIfNull(expression);
ArgumentNullException.ThrowIfNull(value);
var memberExpression = expression?.Body as MemberExpression;
var property = memberExpression?.Member as PropertyInfo;
var logicalName = property?.GetCustomAttribute<LogicalNameAttribute>()?.Value ?? throw new Exception();
_list.Add($"{logicalName} : {value}");
}
public string Output() => string.Join(", ", _list);
}
ラムダ式の引数の型がTargerClass
へ指定した総称型になってるので、それに関係のないプロパティ・名を指定するミスはなくなるかと思います。
最後に
これ以外の改善案は現状わからないですね。
例えば速度面とかは度外視な改善案なので、どう実装するかはその時のスキルや環境に応じて決めていく感じでしょうか。