问题描述
我有以下课程:
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
作为它的输出。
解决方法
我将编写一个接受 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);
}