Find and remove parameter declaration inside Expression.Block - c#

I know how to replace a parameter with ExpressionVisitor but I was wondering if there's a way to remove a parameter from a Expression.Block.
Ideally I should crawl the entire Expression tree and remove the parameter every time it is declared inside a Block.
Any idea how to do that with ExpressionVisitor?

A simple class to remove local variables from a BlockExpression and replace them with whatever you want.
public class BlockVariableRemover : ExpressionVisitor
{
private readonly Dictionary<Expression, Expression> replaces = new Dictionary<Expression, Expression>();
public readonly Func<ParameterExpression, int, Expression> Replacer;
public BlockVariableRemover(Func<ParameterExpression, int, Expression> replacer)
{
Replacer = replacer;
}
protected override Expression VisitBlock(BlockExpression node)
{
var removed = new List<Expression>();
var variables = node.Variables.ToList();
for (int i = 0; i < variables.Count; i++)
{
var variable = variables[i];
var to = Replacer(variable, i);
if (to != variable)
{
removed.Add(variable);
replaces.Add(variable, to);
variables.RemoveAt(i);
i--;
}
}
if (removed.Count == 0)
{
return base.VisitBlock(node);
}
var expressions = node.Expressions.ToArray();
for (int i = 0; i < expressions.Length; i++)
{
expressions[i] = Visit(expressions[i]);
}
foreach (var rem in removed)
{
replaces.Remove(rem);
}
return Expression.Block(variables, expressions);
}
public override Expression Visit(Expression node)
{
Expression to;
if (node != null && replaces.TryGetValue(node, out to))
{
return base.Visit(to);
}
return base.Visit(node);
}
}
Use it like:
Expression<Func<int, int>> exp;
{
var var1 = Expression.Variable(typeof(int), "var1");
var var2 = Expression.Variable(typeof(long), "var2");
var par1 = Expression.Parameter(typeof(int), "par1");
var block = Expression.Block(new[] { var1, var2 }, Expression.Increment(var1));
exp = Expression.Lambda<Func<int, int>>(block, par1);
// Test
var compiled = exp.Compile();
Console.WriteLine(compiled(10));
}
// Begin replace
{
var par1 = exp.Parameters[0];
var block2 = new BlockVariableRemover(
// ix is the index of the variable,
// return x if you don't want to modify,
// return whatever you want (even Expression.Empty()) to do
// a replace
(x, ix) => ix == 0 && x.Type == typeof(int) ? par1 : x)
.Visit(exp.Body);
// Final result
var exp2 = Expression.Lambda<Func<int, int>>(block2, par1);
// Test
var compiled = exp2.Compile();
Console.WriteLine(compiled(10));
}

Related

Record 'Lenses' - expression tree for with expression

Is there a way to build an expression tree for the new with operator?
I am trying to implement a 'Lens' feature for records that will only need a selector and will auto generate the mutator
My goal is to convert from a 'selector':
Expression<Func<T, TMember>> expression (ie employee => employee.Name)
To a 'mutator':
(employee, newName) => employee with { Name = newName }
I did manage to do this for the simple case above, see my answer below, however that will not work for a nested case ie:
record Employee(string Name, int Age);
record Manager(String Name, Employee Employee);
Here I want to change ie from
manager => manager.Employee.Name
to
(manager, newEmployeeName) => manager with { Employee = manager.Employee with { Name = newEmployeeName}}
Any help ?
CalcMutator method that can deal with nested properties would look something like this
static Func<T, TMember, T> CalcMutator(Expression<Func<T, TMember>> expression)
{
var typeParam = expression.Parameters.First();
var valueParam = Expression.Parameter(typeof(TMember), "v");
var variables = new List<ParameterExpression>();
var blockExpressions = new List<Expression>();
var property = (MemberExpression)expression.Body;
Expression currentValue = valueParam;
var index = 0;
while (property != null)
{
var variable = Expression.Variable(property.Expression.Type, $"v_{index}");
variables.Add(variable);
var cloneMethod = property.Expression.Type.GetMethod("<Clone>$");
if (cloneMethod is null) throw new Exception($"CalcMutatorNo Clone method on {typeof(T)}");
var cloneCall = Expression.Call(property.Expression, cloneMethod);
var assignClonedToVariable = Expression.Assign(variable, cloneCall);
var accessVariableProperty = Expression.MakeMemberAccess(variable, property.Member);
var assignVariablePropertyValue = Expression.Assign(accessVariableProperty, currentValue);
blockExpressions.Add(assignClonedToVariable);
blockExpressions.Add(assignVariablePropertyValue);
property = property.Expression as MemberExpression;
currentValue = variable;
index++;
}
// Return root object
blockExpressions.Add(currentValue);
var block = Expression.Block(variables, blockExpressions);
var assignLambda = (Expression<Func<T, TMember, T>>)Expression.Lambda(block, typeParam, valueParam);
return assignLambda.Compile();
}
Please keep in mind that Cache implemented with ImmutableDictionary is not thread safe. If you want to ensure that the cached expressions can safely be used in multi-threaded environments, it's better to use ConcurrentDictionary for the cache instead or to apply some synchronization primitives around ImmutableDictionary.
Following the lead from #JL0PD I ended up converting:
t => t.Member (ie employee => employee.Name)
into:
(t, v) => {
var c = t.<Clone>$();
c.Member = v;
return c;
}
ie:
(employee, newName) => {
var c = employee.<Clone>$();
c.Name=newName;
return c;
}
Below is a full implemetation of a record Lens including caching of delegates
Note that this does not cover nested mutators so my question above still stands
static class RecLens<T, TMember> {
public static (Func<T, TMember> Selector, Func<T, TMember, T> Mutator) Get(Expression<Func<T, TMember>> expression) {
if (!IsExpressionValid(expression.Body)) throw new Exception($"Lens Invalid expression ({expression})");
// create unique cache key, calc same key for x=>x.p and y=>y.p
var exprStr = expression.Body.ToString();
var dotPos = exprStr.IndexOf(Type.Delimiter);
var cacheKey = typeof(T).FullName + '|' + (dotPos > 0 ? exprStr.Remove(0, exprStr.IndexOf(Type.Delimiter) + 1) : "root");
if (!Cache.TryGetValue(cacheKey, out var res)) {
res = (expression.Compile(), CalcMutator(expression));
Cache = Cache.Add(cacheKey, res);
}
return res;
}
// key: "{srcType.FullName}|{member}" , ie: "Test.Organization|DevelopmentDepartment.Manager"
static ImmutableDictionary<string, (Func<T, TMember>, Func<T, TMember, T>)> Cache = ImmutableDictionary<string, (Func<T, TMember>, Func<T, TMember, T>)>.Empty;
// create delegate: (t, v) => { var c=t.<Clone>$(); c.Member = v; return c; }
static Func<T, TMember, T> CalcMutator(Expression<Func<T, TMember>> expression) {
var result = Expression.Variable(typeof(T), "c");
var typeParam = Expression.Parameter(typeof(T), "t");
var valueParam = Expression.Parameter(typeof(TMember), "v");
var cloneMethod = typeof(T).GetMethod("<Clone>$");
if (cloneMethod is null) throw new Exception($"CalcMutatorNo Clone method on {typeof(T)}");
var cloneCall = Expression.Call(typeParam, cloneMethod);
var assignResult = Expression.Assign(result, cloneCall);
var memberInfo = (expression.Body as MemberExpression)!.Member;
var resultMemberAccess = Expression.MakeMemberAccess(result, memberInfo);
var assign = Expression.Assign(resultMemberAccess, valueParam);
var block = Expression.Block(new[] { result }, assignResult, assign, result);
var assignLambda = (Expression<Func<T, TMember, T>>)Expression.Lambda(block, typeParam, valueParam);
return assignLambda.Compile();
}
// verify that expr is a member expression of its parameter
static bool IsExpressionValid(Expression expr, bool first = true) {
if (expr is ParameterExpression) return !first;
if (expr is MemberExpression memberExpr && memberExpr.Expression is object) return IsExpressionValid(memberExpr.Expression, false);
return false;
}
}
To use:
record Employee(string Name, int Age);
var (Selector, Mutator) = RecLens<Employee, string>.Get(e => e.Name);
var dave = new Employee("Dave", 30);
var name = Selector(dave); // "Dave"
var john = Mutator(dave, "John"); // Employee("John", 30)

C# Expression, Accessing Property

I am trying to build a filter method for IQueryable Type,
I could achieve this for ex:
query = query.Filter(UsersListId, e => e.UserId);
public static IQueryable<T> Filter<T, TSearch>(this IQueryable<T> query, List<TSearch> list, Expression<Func<T, TSearch>> props)
{
if (list == null || list.Count == 0)
{
return query;
}
var propertyPath = props.Body.ToString().Replace(props.Parameters[0] + ".", string.Empty);
var containsMethod = typeof(List<TSearch>).GetMethod("Contains", new Type[] { typeof(TSearch) });
ConstantExpression constlist = Expression.Constant(list);
var param = Expression.Parameter(typeof(T), "Entity");
var newvalue = GetPropertyValue(param, propertyPath);
var body = Expression.Call(constlist, containsMethod, newvalue);
var exp = Expression.Lambda<Func<T, bool>>(body, param);
return query.Where(exp);
}
public static MemberExpression GetPropertyValue(ParameterExpression parExp, string propertyPath)
{
var properties = propertyPath.Split('.').ToArray();
var value = Expression.Property(parExp, properties[0]);
for (int i = 1; i < properties.Length; i++)
{
value = Expression.Property(value, properties[i]);
}
return value;
}
this code takes the propertyPath (ex: x.User.Department.Id)
and returns the value,
The problems here:
I cannot pass a nullable object if the list isn't nullable too
I am not quite sure weather it's the right way to access properties
so my question is what is the solution for these problems?

How to verify that an Expression is a Member Access lambda

Inside a function receiving an expression, how can I check if the expression is a member access lambda ?
bool F<TSrc, TVal>(TSrc src, Expression<Func<TSrc, TVal>> exp) {
bool isMememberAccess = ???
if (!isMememberAccess) return false;
...
return true;
}
so that:
var emp = new Employee();
var res = F(emp, x => x.FirstName); // returns true;
var org = new Organization();
var res = F(org, x => x.Sales.Manager.FirstName); // returns true;
int i=0;
var res = F(emp, x => i); // returns false - expression is not a member access
I tried to check for exp.Body.NodeType == ExpressionType.MemberAccess but that returns true in both cases.
Any help ?
Captured lambda variables are stored in a generated struct type, passed in as a static constant. Your expression argument x => i is roughly equivalent to;
public struct locals
{
public int i;
}
var localState = new locals { i = i };
Expression.Lambda<Func<Employee, int>>(
Expression.MakeMemberAccess(
Expression.Constant(localState, typeof(locals)),
typeof(locals).GetField(nameof(locals.i))
),
Expression.Parameter(typeof(Employee),"x")
);
As you can see, this also includes a member expression. What you want to prove, is that the member expression is based on the type of the expression parameter.
bool F<TSrc, TVal>(TSrc src, Expression<Func<TSrc, TVal>> exp) {
var isMememberAccess = exp.Body is MemberExpression member
&& member.Expression is ParameterExpression parameter
&& parameter.Type == typeof(TSrc);
...

Dynamic Linq using SqlMethods.Like

I'm trying to build a dynamic Linq to Sql query and it's going pretty well, except for invoking the SqlMethods.Like method. My code is below and the body of the linq statement being generated looks like this:
Body = {((((log.ClientCode == "C1") OrElse
(log.ClientCode == "C2")) AndAlso
(log.Source == "S1")) AndAlso Like("Message", "%1%"))}
As you can see, it attempts to call "Like" without the SqlMethods class. Any idea what I'm doing wrong??
public IEnumerable<ILog> Get(int pageNumber, int pageCount,
List<string> clientCodes, List<string> sources, List<LogLevel> logLevels,
string messageContains, string userNameContains,
DateTime? dateStart, DateTime? dateEnd)
{
var expressions = new List<Expression>();
ParameterExpression pe = Expression.Parameter(typeof(Data.Logging.Log), "log");
if (clientCodes != null && clientCodes.Count > 0)
{
expressions.Add(CreateClientCodeExpression(pe, clientCodes));
}
if (sources != null && sources.Count > 0)
{
expressions.Add(CreateSourceExpression(pe, sources));
}
if (logLevels != null && logLevels.Count > 0)
{
expressions.Add(CreateLogLevelExpression(pe, logLevels));
}
if (!string.IsNullOrWhiteSpace(messageContains))
{
expressions.Add(CreateMessageExpression(pe, messageContains));
}
Expression exp = null;
if (expressions.Count > 0)
{
exp = expressions[0];
}
for (var i = 1; i < expressions.Count; i++)
{
exp = Expression.AndAlso(exp, expressions[i]);
}
var predicate = Expression.Lambda<Func<Data.Logging.Log, bool>>(exp, pe);
var results = DbContext.Logs.Where(predicate).ToList();
foreach (var result in results)
{
yield return ConvertDbLogToLog(result);
}
}
private Expression CreateClientCodeExpression(ParameterExpression pe, List<string> clientCodes)
{
Expression result = null;
clientCodes.ForEach(cc =>
{
MemberExpression me = Expression.Property(pe, "ClientCode");
ConstantExpression ce = Expression.Constant(cc);
if (result == null) { result = Expression.Equal(me, ce); }
else { result = Expression.OrElse(result, Expression.Equal(me, ce)); }
});
return result;
}
private Expression CreateSourceExpression(ParameterExpression pe, List<string> sources)
{
Expression result = null;
sources.ForEach(s =>
{
MemberExpression me = Expression.Property(pe, "Source");
ConstantExpression ce = Expression.Constant(s);
if (result == null) { result = Expression.Equal(me, ce); }
else { result = Expression.OrElse(result, Expression.Equal(me, ce)); }
});
return result;
}
private Expression CreateLogLevelExpression(ParameterExpression pe, List<LogLevel> logLevels)
{
Expression result = null;
logLevels.ForEach(l =>
{
MemberExpression me = Expression.Property(pe, "LogLevel");
ConstantExpression ce = Expression.Constant(l.ToString());
if (result == null) { result = Expression.Equal(me, ce); }
else { result = Expression.OrElse(result, Expression.Equal(me, ce)); }
});
return result;
}
private MethodCallExpression CreateMessageExpression(ParameterExpression pe, string message)
{
return Expression.Call(typeof(SqlMethods).GetMethod("Like", new[] { typeof(string), typeof(string) }),
Expression.Constant("Message"), Expression.Constant(string.Format("%{0}%", message)));
}
Actually, the 'Contains' operator is not always sufficient. For example, if you want to search something like this: 'first%last'. The '%' in the string will be taken literally instead of a wildcard as intended. In order to use the 'SqlMethods.Like' operator you can use the following:
public static MethodCallExpression Like(this ParameterExpression pe, string value)
{
var prop = Expression.Property(pe, pe.Name);
return Expression.Call(typeof(SqlMethods), "Like", null, prop, Expression.Constant(value));
}
You can skip the Like invocation, and use Contains, which both Linq-to-SQL and Linq-to-Entities correctly translate to a LIKE statement. You even save the quoting! (which, btw, you are doing wrong).
private MethodCallExpression CreateMessageExpression(ParameterExpression pe, string message)
{
return Expression.Call(Expression.Property(pe, "Message"), "Contains", null, Expression.Constant(message));
}

Lambda Expression for dynamic Object

I am trying to build a Lambda Expression for a table that has been created at run time.
The Expression is build fine but when I call Compile() method I get this error
"ParameterExpression of type 'cseval.Item' cannot be used for delegate parameter of type 'System.Object'"
this is my function
public Func<dynamic, Boolean> GetWhereExp(List<WhereCondition> SearchFieldList, dynamic item)
{
ParameterExpression pe = Expression.Parameter(item.GetType(), "c");
Expression combined = null;
if (SearchFieldList != null)
{
foreach (WhereCondition fieldItem in SearchFieldList)
{
//Expression for accessing Fields name property
Expression columnNameProperty = Expression.Property(pe, fieldItem.ColumName);
//the name constant to match
Expression columnValue = Expression.Constant(fieldItem.Value);
//the first expression: PatientantLastName = ?
Expression e1 = Expression.Equal(columnNameProperty, columnValue);
if (combined == null)
{
combined = e;
}
else
{
combined = Expression.And(combined, e);
}
}
}
var result = Expression.Lambda<Func<dynamic, bool>>(combined, pe);
return result.Compile();
}
I've changed dynamic to generics, this code works for me:
public Func<T, Boolean> GetWhereExp<T>(List<WhereCondition> SearchFieldList, T item)
{
var pe = Expression.Parameter(item.GetType(), "c");
Expression combined = null;
if (SearchFieldList != null)
{
foreach (var fieldItem in SearchFieldList)
{
var columnNameProperty = Expression.Property(pe, fieldItem.ColumName);
var columnValue = Expression.Constant(fieldItem.Value);
var e1 = Expression.Equal(columnNameProperty, columnValue);
combined = combined == null ? e1 : Expression.And(combined, e1);
}
}
var result = Expression.Lambda<Func<T, bool>>(combined, pe);
return result.Compile();
}
Small remark: your method returns function, not an expression, so the name 'GetWhereExp' is slightly incorrect. If you want to return function, imho, it's better to use reflection.
UPD: I use this code to test:
var expressions = new List<WhereCondition>
{
new WhereCondition("Column1", "xxx"),
new WhereCondition("Column2", "yyy"),
};
var item = new
{
Column1 = "xxx",
Column2 = "yyy"
};
var func = LinqExpr.GetWhereExp(expressions, (dynamic)item);
Console.WriteLine(new[] {item}.Count(a => func(a)));

Categories