Search code examples
c#.netlinqgenericsreflection

Get DescriptionAttribute via reflection


I have this:

public class Demo
{
  [Description("Hello World!")]
  public int SomeProp { get; set; }
}

I want the description. So I wrote this:

public string? GetDescription<TType, TProperty>(Expression<Func<TType, TProperty>> expression)
  where TType : class
{
  var memberName  = ((MemberExpression)expression.Body).Member.Name;
  var description = typeof(TType).GetMember(memberName).First()
                    .GetCustomAttribute<DescriptionAttribute>()
                    ?.Description;
  return description;
}

Which can be used like this:

var description = GetDescription<Demo, string>(x => x.SomeProp);

But what I want is this:

var description = GetDescription<Demo>(x => x.SomeProp);   // no TProperty

I hoped type inference would infer TProperty and simplify the callsite, but I can't compile without it ("Using the generic method requires 2 type arguments").

How can I do this?


Solution

  • One way is to just take a Func<TType, object>. When the property type is not object, the root expression node will be a Convert expression, so you just need to check for that.

    public static string? GetDescription<TType>(Expression<Func<TType, object?>> expression)
      where TType : class
    {
        var body = expression.Body;
        string memberName;
        if (body is MemberExpression exp1) {
            memberName = exp1.Member.Name;
        } else if (body is UnaryExpression unary && 
                   unary.NodeType == ExpressionType.Convert &&
                   unary.Operand is MemberExpression exp2) {
            memberName = exp2.Member.Name;
        } else {
            throw new NotSupportedException();
        }
        var description = typeof(TType).GetMember(memberName).First()
                        .GetCustomAttribute<DescriptionAttribute>()
                        ?.Description;
        return description;
    }
    

    Or, you can change the use-site and explicitly write the parameter type of the lambda, instead of explicitly writing the type arguments of GetDescription.

    GetDescription((Demo x) => x.SomeProp)
    

    Side note:

    You already got the MemberInfo from the expression, so why not just get the attribute straight from there?

    public static string? GetDescription<TType>(Expression<Func<TType, object>> expression)
      where TType : class
    {
        var body = expression.Body;
        MemberInfo member;
        if (body is MemberExpression exp1) {
            member = exp1.Member;
        } else if (body is UnaryExpression unary && 
                   unary.NodeType == ExpressionType.Convert &&
                   unary.Operand is MemberExpression exp2) {
            member = exp2.Member;
        } else {
            throw new NotSupportedException();
        }
        var description = member
                        .GetCustomAttribute<DescriptionAttribute>()
                        ?.Description;
        return description;
    }