Как получить аттрибут свойства типа с помощью лямбда выражения?

Имеется расширениe для типа, которое используется для возврата атрибутов свойств:

public static T GetAttribute<T>(this Type type, string propertyName) where T : Attribute
{
    return type
        .GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance)
        ?.GetCustomAttributes(typeof(T), false)
        .Cast<T>().FirstOrDefault();
}

var displayNameAttribute = typeof(Person)
    .GetAttribute<DisplayNameAttribute>(nameof(Person.Id));

Недостаток в том, что имя свойства передаётся как текст и не связано с типом.
Можно ли каким-либо образом преобразовать это расширение для использования с лямбда выражением?
Хотелось бы использовать расширение таким образом:

var displayNameAttribute = typeof(Person)
    .GetAttribute<DisplayNameAttribute>(p => p.Id);

На данный момент всё что я смог сделать (и то с помощью @AlexanderPetrov):

public static class TypeUtils
{
    public static TAttr GetAttribute<TAttr, TEntity>(
            Expression<Func<TEntity, object>> argumentFunc)
        where TAttr : Attribute
    {
        string propertyName = GetMemberInfo(argumentFunc).Member.Name;

        return typeof(TEntity)
            .GetProperty(propertyName,
                BindingFlags.Public | BindingFlags.Instance)
            ?.GetCustomAttributes(typeof(TAttr), false)
            .Cast<TAttr>().FirstOrDefault();
    }

    private static MemberExpression GetMemberInfo(Expression method)
    {
        if (method is not LambdaExpression lambda)
        {
            throw new ArgumentNullException(nameof(method));
        }

        MemberExpression memberExpr = null;

        if (lambda.Body.NodeType == ExpressionType.Convert)
        {
            memberExpr = ((UnaryExpression)lambda.Body)
                         .Operand as MemberExpression;
        }
        else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
        {
            memberExpr = lambda.Body as MemberExpression;
        }

        return memberExpr
               ?? throw new ArgumentException(null, nameof(method));
    }
}

// используется так
var attr = TypeUtils.GetAttribute<DisplayNameAttribute, Person>(p => p.Id);

Но у меня не получается сделать это именно как расширение для типа.


Ответы (1 шт):

Автор решения: Alexander Petrov
public static T GetAttribute<T, T2>(this Type type, Expression<Func<T2>> func)
    where T : Attribute
{
    var expressionBody = (MemberExpression)func.Body;
    string propertyName = expressionBody.Member.Name;

    return type
        .GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance)
        ?.GetCustomAttributes(typeof(T), false)
        .Cast<T>().FirstOrDefault();
}

Использование:

var p = new Person();

var displayNameAttribute = typeof(Person)
    .GetAttribute<DisplayNameAttribute, int>(() => p.Id);

Console.WriteLine(displayNameAttribute.DisplayName);

Недостаток в том, что нужно явно указывать тип свойства в вызове метода. В данном случае указан int.


Можно объявить метод так:

public static T GetAttribute<T, T2, T3>(this Type type, Expression<Func<T2, T3>> func)

В этом случае становится не нужен экземпляр var p = new Person();.
Но дополнительно придётся указать тип в вызове метода:

var displayNameAttribute = typeof(Person)
    .GetAttribute<DisplayNameAttribute, Person, int>(p => p.Id);
→ Ссылка