3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C# - プロパティに付与した属性の値を取得する処理のあるメソッドの引数に任意文字列を与えたくない

Last updated at Posted at 2024-10-27

初めに

この記事で使用している.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へ指定した総称型になってるので、それに関係のないプロパティ・名を指定するミスはなくなるかと思います。

最後に

これ以外の改善案は現状わからないですね。
例えば速度面とかは度外視な改善案なので、どう実装するかはその時のスキルや環境に応じて決めていく感じでしょうか。

参考

関連

3
2
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?