seamaster
seamaster

Reputation: 391

C# - How do you get the nameof a property of a composite object?

I have a Company class that has a composite object as a property that has company object as its property and so on for many levels. I need to get the name of the property at the deepest level with the least amount of code.

Here's a pseudocode for the case:

class Company
{
    public Person Boss
    {
        public Document CV
        {
             public string Title;
        }
    }
}

I need to get a string result of "Company.Boss.CV.Title" with as less code as possible. The naive way of doing this would be to try nameof(Company.Boss.CV.Title) but as we know this will result in just "Title".

I need to work with types not pure strings to allow the compiler to throw an error whenever someone changes the property names. Obviously something like:

string.Join('.', nameof(Company), nameof(Company.Boss), nameof(Company.Boss.CV), nameof(Company.Boss.CV.Title))

will work correctly 100% of the time but there is too much boilerplate code. Any suggestions are highly appreciated!

Upvotes: 3

Views: 844

Answers (1)

phuzi
phuzi

Reputation: 13060

You could use an Expression and decompose it, something like:

Expression<Func<Company, object>> myExpr = (c) => c.Boss.CV.Title;

var expr = myExpr.Body;
var sb = new StringBuilder();

// Traverse the expression
while (expr != null)
{
    switch (expr.NodeType){
        case ExpressionType.MemberAccess:
            var memberExpr = ((MemberExpression)expr);
            sb.Insert(0, "." + memberExpr.Member.Name, 1);
            expr = memberExpr.Expression;
            break;
        case ExpressionType.Parameter:
            var paramExpr = ((ParameterExpression)expr);
            parts.Add(paramExpr.Type.Name);
            sb.Insert(0, paramExpr.Type.Name, 1);
            expr = null;
            break;
        default:
            expr = null;
            break;
    }
}

var propertyPath = sb.ToString(); // "Company.Boss.CV.Title"

Using ExpressionVisitor

Implementing a custom ExpressionVisitor can help with clarity of the code.

public class MyExpressionVisitor : ExpressionVisitor{
    private List<string> parts;

    protected override Expression VisitMember(MemberExpression node)
    {
        parts.Add(node.Member.Name);
        return base.VisitMember(node);
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        parts.Add(node.Type.Name);
        return base.VisitParameter(node);
    }

    public string GetPath(Expression e)
    {
        parts = new List<string>();
        Visit(e is LambdaExpression ? ((LambdaExpression)e).Body : e);
        parts.Reverse();
        return string.Join(".", parts);
    }
}

You can then call it using

Expression<Func<Company, object>> myExpr = (c) => c.Boss.CV.Title;
var myVisitor = new MyExpressionVisitor();
var propertyPath = myVisitor.GetPath(myExpr);

Upvotes: 3

Related Questions