I need to extract all member accesses to expression parameter (and make sure, that they are not nested). For instance, for expression: a => a.A + a.B
I need to receive list {"A", "B"}
. For expression a => a.B.C
exception should be thrown, as we have nested member access.
I wrote the following class, but I'm not too satisfied with it. Mainly because I don't like pattern "Call, then access member for result", because it decreases code maintainability and allows introducing hard to find bugs (ie. what if I accidentally access results later without calling etc.)
I'm wondering, if this class might be implemented in such way, so that I could call:
List<string> members = new MemberExpressionVisitor<A, bool>().GetMembers(a => a.A + a.B);
My current solution follows:
private class MemberExpressionVisitor<T1, T2> : ExpressionVisitor
{
private Expression<Func<T1, T2>> originalExpression = null;
private HashSet<string> accessedMembers = null;
public override Expression Visit(Expression node)
{
if (originalExpression == null)
{
if (node is Expression<Func<T1, T2>> startExpression)
{
originalExpression = startExpression;
accessedMembers = new HashSet<string>();
var result = base.Visit(node);
originalExpression = null;
return result;
}
else
{
throw new InvalidOperationException($"Passed expression must be of type Expression<Func<{typeof(T1).Name},{typeof(T2).Name}>>!");
}
}
else
{
return base.Visit(node);
}
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression is MemberExpression)
throw new InvalidOperationException("Nested member accesses are not allowed!");
if (node.Expression is ParameterExpression parameterExpression)
{
if (parameterExpression.Name == originalExpression.Parameters[0].Name)
accessedMembers.Add(node.Member.Name);
}
return base.VisitMember(node);
}
public List<string> GetAccessedMembers() => accessedMembers.ToList();
}
Example call:
public class B
{
public int P => 5;
}
public class A
{
public B B => new B();
public int C => 8;
}
static void Main(string[] args)
{
Expression<Func<A, bool>> exp = a => a.B != null && a.C ==8;
MemberExpressionVisitor<A, bool> visitor = new MemberExpressionVisitor<A, bool>();
visitor.Visit(exp);
Console.WriteLine(String.Join(", ", visitor.GetAccessedMembers()));
Console.ReadKey();
}
Edit: Safer, but with clumsy "processing" field.
private class MemberExpressionExtractor : ExpressionVisitor
{
private bool processing = false;
private string parameterName;
private readonly HashSet<string> accessedMembers = new HashSet<string>();
public List<string> ExtractMembers<T1, T2>(Expression<Func<T1, T2>> expression)
{
try
{
processing = true;
parameterName = expression.Parameters[0].Name;
accessedMembers.Clear();
Visit(expression);
}
finally
{
processing = false;
}
return accessedMembers.ToList();
}
public override Expression Visit(Expression node)
{
if (!processing)
throw new InvalidOperationException("Use ExtractMembers<T1, T2> instead.");
return base.Visit(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression is MemberExpression)
throw new InvalidOperationException("Nested member accesses are not allowed!");
if (node.Expression is ParameterExpression parameterExpression)
{
if (parameterExpression.Name == parameterName)
accessedMembers.Add(node.Member.Name);
}
return base.VisitMember(node);
}
}
```