Reputation: 179
I have a class:
public class Student
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("age")]
public int Age { get; set; }
[JsonProperty("country")]
public string Country { get; set; }
}
And I have a method:
public static List<string> PrintPropertyNames<T>(params Expression<Func<T, object>>[] properties)
{
var list = new List<string>();
foreach (var p in properties)
{
if (p.Body is MemberExpression)
{
var e = (MemberExpression)p.Body;
list.Add(((JsonPropertyAttribute)e.Member.GetCustomAttribute(typeof(JsonPropertyAttribute))).PropertyName);
}
else
{
var e = (MemberExpression)((UnaryExpression)p.Body).Operand;
list.Add(((JsonPropertyAttribute)e.Member.GetCustomAttribute(typeof(JsonPropertyAttribute))).PropertyName);
}
}
return list;
}
Which I call like this:
Console.WriteLine(string.Join(" ", PrintPropertyNames<Student>(x => x.Age, x => x.Country)));
Now, I want to modify my method definition to take only one parameter, but I can't figure out how to do it.
I tried doing something like this:
public static List<string> PrintPropertyNames2<T>(Expression<Func<T, object>>[] properties)
Which I call like this:
Console.WriteLine(string.Join(" ", PrintPropertyNames2<Student>(new Expression<Func<Student, object>>[] { x => x.Age, x => x.Country })));
I tried simplifying it to:
Console.WriteLine(string.Join(" ", PrintPropertyNames2<Student>(new [] { x => x.Age, x => x.Country })));
But the compiler couldn't find the best suitable type. So I have to explicitly write the type, which looks ugly and not what I really want anyways. I need it generic.
What I want to do in the final version is the following:
Console.WriteLine(string.Join(" ", PrintPropertyNames<Student>(x => x.Age && x.Country && x.Name)));
(output should be - age country name
)
I'm not sure if that's possible, but I want to put all my properties inside a single expression and obtain their json attribute value at once.
Upvotes: 0
Views: 270
Reputation: 42235
For starters, you can't use x => x.Age && x.Country && x.Name
-- Age
is an int
and Name
is a string
, and you can't combine those with &&
, so you'll get a compiler error. But we can use +
instead as string concatentation, or return a new { x.Name, x.Age, x.Country }
or new object[] { x.Name, x.Age, x.Country }
.
Either way, the easiest way to do this is to use an ExpressionVisitor
to find all MemberAccess
expressions which access a property on our input Student
, wherever they're buried in the expression:
public class FindPropertiesVisitor : ExpressionVisitor
{
private readonly Expression parameter;
public List<string> Names { get; } = new List<string>();
public FindPropertiesVisitor(Expression parameter) => this.parameter = parameter;
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression == parameter)
{
Names.Add(node.Member.GetCustomAttribute<JsonPropertyAttribute>().PropertyName);
}
return node;
}
}
Using this is pretty straightforward:
public static List<string> FindPropertyNames<T>(Expression<Func<T, object>> expr)
{
var visitor = new FindPropertiesVisitor(expr.Parameters[0]);
visitor.Visit(expr);
return visitor.Names;
}
We pass in expr.Parameters[0]
as the Student
expression, which we want to find member accesses on.
You can then call it with any expression which accesses those properties in any way, e.g.:
var names = FindPropertyNames<Student>(x => new { x.Name, x.Age, x.Country });
var names = FindPropertyNames<Student>(x => new object[] { x.Name, x.Age, x.Country });
var names = FindPropertyNames<Student>(x => x.Name + x.Age + x.Country);
Upvotes: 2