【问题标题】:Accessing nested properties and collections using Expression使用表达式访问嵌套属性和集合
【发布时间】:2021-06-08 11:51:06
【问题描述】:

我有以下课程:

public class Person
{
    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("age")]
    public int Age { get; set; }

    [JsonProperty("country")]
    public string Country { get; set; }

    [JsonProperty("profession")]
    public Profession Profession { get; set; }

    [JsonProperty("hobbies")]
    public List<string> Hobbies { get; set; }
}

public class Profession
{
    [JsonProperty("name")]
    public string ProfessionName { get; set; }

    [JsonProperty("industry")]
    public string Industry { get; set; }

    [JsonProperty("salary")]
    public string AverageSalary { get; set; }

    [JsonProperty("activities")]
    public List<WorkActivity> WorkActivities { get; set; }
}

public class WorkActivity
{
    [JsonProperty("id")]
    public int Id { get; set; }

    [JsonProperty("rooms")]
    public List<string> Rooms { get; set; }
}

public class PropertiesVisitor : ExpressionVisitor
{
    private readonly Expression param;

    public List<string> Names { get; } = new List<string>();

    public PropertiesVisitor(Expression parameter)
    {
        param = parameter;
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Expression == param)
        {
            Names.Add(node.Member.GetCustomAttribute<JsonPropertyAttribute>().PropertyName);
        }

        return base.VisitMember(node);
    }
}

我在我的应用程序中这样调用方法:

private static List<string> FindPropertyNames<T>(Expression<Func<T, object>> e)
{
    var visitor = new PropertiesVisitor(e.Parameters[0]);
    visitor.Visit(e);
    return visitor.Names;
}

public static void Main(string[] args)
{
    var names = FindPropertyNames<Person>(x => new { x.Age, x.Country, x.Profession.AverageSalary, x.Hobbies });
    Console.WriteLine(string.Join(" ", names));
}

它工作正常,除了一个小细节 - 它不检查嵌套属性。输出如下:age country profession hobbies。我希望它是age country profession.salary hobbies

我一直在尝试使用不同的方法来解决此问题,但我无法完全做到这一点。我尝试了以下方法:

public static MemberExpression GetMemberExpression(Expression e)
{
    if (e is MemberExpression)
    {
        return (MemberExpression)e;
    }
    else if (e is LambdaExpression)
    {
        var le = e as LambdaExpression;
        if (le.Body is MemberExpression)
        {
            return (MemberExpression)le.Body;
        }
        else if (le.Body is UnaryExpression)
        {
            return (MemberExpression)((UnaryExpression)le.Body).Operand;
        }
    }
    return null;
}

public static string GetPropertyPath<T>(Expression<Func<T, object>> expr)
{
    var path = new StringBuilder();
    MemberExpression me = GetMemberExpression(expr);

    do
    {
        if (path.Length > 0)
        {
            path.Insert(0, ".");
        }
        path.Insert(0, me.Member.GetCustomAttribute<JsonPropertyAttribute>().PropertyName);
        me = GetMemberExpression(me.Expression);
    }
    while (me != null);
    return path.ToString();
}

它可以完成这项工作 - 但它不能占用多个属性。

我这样称呼它:

var x = GetPropertyPath<Person>(p => p.Profession.AverageSalary);
Console.WriteLine(x);

我希望能够发送多个属性,就像在第一个版本中一样。另外,我不确定如何将以下person.Profession.WorkActivities.Rooms 作为参数传递,因为它是一个列表。我想得到profession.activities.rooms 作为它的输出。

【问题讨论】:

    标签: c# lambda reflection expression expression-trees


    【解决方案1】:

    我会编写一个方法,它接受Expression 并返回链中的所有成员:

    public static IEnumerable<MemberExpression> MemberClauses(this Expression expr) {
        if (expr is not MemberExpression mexpr) {
            yield break;
        }
        foreach (var item in MemberClauses(mexpr.Expression)) {
            yield return item;
        }
        yield return mexpr;
    }
    

    然后,您可以打开整个方法链和 LINQ 以获取 JSON 属性名称:

    public class PropertiesVisitor : ExpressionVisitor {
        private readonly Expression param;
    
        public List<string> Names { get; } = new List<string>();
    
        public PropertiesVisitor(Expression parameter) => param = parameter;
    
        [return: NotNullIfNotNull("node")]
        public override Expression Visit(Expression node) {
            var chain = node.MemberClauses().ToList();
            if (chain.Any() && chain.First().Expression == param) {
                var name = string.Join(".", chain.Select(
                    mexpr => mexpr.Member.GetCustomAttribute<JsonPropertyAttribute>().PropertyName
                ));
                Names.Add(name);
                return node;
            } else {
                return base.Visit(node);
            }
        }
    }
    

    然后就可以得到属性路径如下:

    public static class Functions {
        public static List<string> GetPropertyPath<T>(Expression<Func<Person, T>> expr) {
            var visitor = new PropertiesVisitor(expr.Parameters[0]);
            visitor.Visit(expr);
            return visitor.Names;
        }
    }
    
    var names = Functions.GetPropertyPath(p => new { p.Age, p.Country, p.Profession.AverageSalary, p.Hobbies });
    foreach (var name in names) {
        Console.WriteLine(name);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-04-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多