Replace parameter in lambda expression
I would do it this way:
Write a parameter-replacer expression-visitor that manipulates the original expression as follows:
- Gets rid of the parameter you don't want entirely from the lambda signature.
- Replaces all uses of the parameter with the desired indexer expression.
Here's a quick and dirty sample I whipped up based on my earlier answer on a different question:
public static class ParameterReplacer
{
// Produces an expression identical to 'expression'
// except with 'source' parameter replaced with 'target' expression.
public static Expression<TOutput> Replace<TInput, TOutput>
(Expression<TInput> expression,
ParameterExpression source,
Expression target)
{
return new ParameterReplacerVisitor<TOutput>(source, target)
.VisitAndConvert(expression);
}
private class ParameterReplacerVisitor<TOutput> : ExpressionVisitor
{
private ParameterExpression _source;
private Expression _target;
public ParameterReplacerVisitor
(ParameterExpression source, Expression target)
{
_source = source;
_target = target;
}
internal Expression<TOutput> VisitAndConvert<T>(Expression<T> root)
{
return (Expression<TOutput>)VisitLambda(root);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
// Leave all parameters alone except the one we want to replace.
var parameters = node.Parameters
.Where(p => p != _source);
return Expression.Lambda<TOutput>(Visit(node.Body), parameters);
}
protected override Expression VisitParameter(ParameterExpression node)
{
// Replace the source with the target, visit other params as usual.
return node == _source ? _target : base.VisitParameter(node);
}
}
}
Usage for your scenario (tested):
var zeroIndexIndexer = Expression.MakeIndex
(Expression.Constant(foos),
typeof(List<Foo>).GetProperty("Item"),
new[] { Expression.Constant(0) });
// .ToString() of the below looks like the following:
// () => (value(System.Collections.Generic.List`1[App.Foo]).Item[0].a
// * value(System.Collections.Generic.List`1[App.Foo]).Item[0].b)
var exp1Clone = ParameterReplacer.Replace<Func<Foo, int>, Func<int>>
(exp0, exp0.Parameters.Single(), zeroIndexIndexer);
Replace parameter type in lambda expression
You need to do a few things for this to work:
- Replace parameter instance both at the
Expression.Lambda
and anywhere they appear in the body - and use the same instance for both. - Change the lambda's delegate type.
- Replace the property expressions.
Here's the code, with added generics:
public static Func<TTarget, bool> Convert<TSource, TTarget>(Expression<Func<TSource, bool>> root)
{
var visitor = new ParameterTypeVisitor<TSource, TTarget>();
var expression = (Expression<Func<TTarget, bool>>)visitor.Visit(root);
return expression.Compile();
}
public class ParameterTypeVisitor<TSource, TTarget> : ExpressionVisitor
{
private ReadOnlyCollection<ParameterExpression> _parameters;
protected override Expression VisitParameter(ParameterExpression node)
{
return _parameters?.FirstOrDefault(p => p.Name == node.Name) ??
(node.Type == typeof(TSource) ? Expression.Parameter(typeof(TTarget), node.Name) : node);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
_parameters = VisitAndConvert<ParameterExpression>(node.Parameters, "VisitLambda");
return Expression.Lambda(Visit(node.Body), _parameters);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Member.DeclaringType == typeof(TSource))
{
return Expression.Property(Visit(node.Expression), node.Member.Name);
}
return base.VisitMember(node);
}
}
Replace parameter into expression
You can walk the expression tree of expr
, and replace all occurrences of b
with paramA
using the approach described in this Q&A: "Combine two lambda expressions with inner expression".
However, if you simply need a lambda expression that uses paramA
as its parameter, it is easier to make a lambda that wraps expr
instead:
var res = (Expression<Func<string,bool>>)Expression.Lambda(
Expression.Invoke(expr, paramA)
, paramA
);
Replace parameter to point to nested parameter in lambda expression
You already solved the concrete issue, so I can't say if what I'm going to propose you is better/more elegant, but for sure is a bit more generic (removed the concrete types/properties/assumptions), hence can be reused for translating similar expressions from different model types.
Here is the code:
public class ExpressionMap
{
private Dictionary<Type, Type> typeMap = new Dictionary<Type, Type>();
private Dictionary<MemberInfo, Expression> memberMap = new Dictionary<MemberInfo, Expression>();
public ExpressionMap Add<TFrom, TTo>()
{
typeMap.Add(typeof(TFrom), typeof(TTo));
return this;
}
public ExpressionMap Add<TFrom, TFromMember, TTo, TToMember>(Expression<Func<TFrom, TFromMember>> from, Expression<Func<TTo, TToMember>> to)
{
memberMap.Add(((MemberExpression)from.Body).Member, to.Body);
return this;
}
public Expression Map(Expression source) => new MapVisitor { map = this }.Visit(source);
private class MapVisitor : ExpressionVisitor
{
public ExpressionMap map;
private Dictionary<Type, ParameterExpression> parameterMap = new Dictionary<Type, ParameterExpression>();
protected override Expression VisitLambda<T>(Expression<T> node)
{
return Expression.Lambda(Visit(node.Body), node.Parameters.Select(Map));
}
protected override Expression VisitParameter(ParameterExpression node) => Map(node);
protected override Expression VisitMember(MemberExpression node)
{
var expression = Visit(node.Expression);
if (expression == node.Expression)
return node;
Expression mappedMember;
if (map.memberMap.TryGetValue(node.Member, out mappedMember))
return Visit(mappedMember);
return Expression.PropertyOrField(expression, node.Member.Name);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Object == null && node.Method.IsGenericMethod)
{
// Static generic method
var arguments = Visit(node.Arguments);
var genericArguments = node.Method.GetGenericArguments().Select(Map).ToArray();
var method = node.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArguments);
return Expression.Call(method, arguments);
}
return base.VisitMethodCall(node);
}
private Type Map(Type type)
{
Type mappedType;
return map.typeMap.TryGetValue(type, out mappedType) ? mappedType : type;
}
private ParameterExpression Map(ParameterExpression parameter)
{
var mappedType = Map(parameter.Type);
ParameterExpression mappedParameter;
if (!parameterMap.TryGetValue(mappedType, out mappedParameter))
parameterMap.Add(mappedType, mappedParameter = Expression.Parameter(mappedType, parameter.Name));
return mappedParameter;
}
}
}
and the usage for your concrete example:
public IEnumerable<DomainColour> GetWhere(Expression<Func<DomainColour, bool>> predicate)
{
var map = new ExpressionMap()
.Add<DomainColour, DtoColour>()
.Add((DomainColour c) => c.People, (DtoColour c) => c.FavouriteColours.Select(fc => fc.Person))
.Add<DomainPerson, DtoPerson>();
var mappedPredicate = ((Expression<Func<DtoColour, bool>>)map.Map(predicate));
return Colours.Where(mappedPredicate.Compile()).Select(c => new DomainColour(c.Name)).ToList();
}
As you can see, it allows you to define a simple mapping from one type to another, and optionally from member of one type to member/expression of another type (as soon as they are compatible) using "fluent" syntax with lambda expressions. The members that have no specified mapping are mapped by name as in the original code.
Once the mappings are defined, the actual processing of course is done by a custom ExpressionVisitor
, similar to yours. The difference is that it maps and consolidates ParameterExpression
s by type, and also translates every static generic method, thus should work also with Queryable
and similar.
How to replace a huge lambda expression with a private method adding a parameter?
private static Action<AuthorizationPolicyBuilder> ConfigBuilderFactory(string param)
{
return builder => builder
.RequireClaim("special_claim", param)
.RequireClaim("claim1", "value1")
...;
}
options.AddPolicy("ExtensivePolicy_A", ConfigBuilderFactory("a"));
options.AddPolicy("ExtensivePolicy_Z", ConfigBuilderFactory("z"));
Replacing parameters in a lambda expression
I used strings, since it was the easisest way for me. You can't manually change the name of the parameter expression (it has the "Name" property, but it is read-only), so you must construct a new expression from pieces. What I did is created a "nameless" parameter (actually, it gets an autogenerated name in this case, which is "Param_0") and then created a new expression almost the same as the old one, but using the new parameter.
public static void Main()
{
String thing = "test";
Expression<Action<String>> expression = c => c.ToUpper();
Delegate del = expression.Compile();
del.DynamicInvoke(thing);
Dictionary<String, Delegate> cache = new Dictionary<String, Delegate>();
cache.Add(GenerateKey(expression), del);
Expression<Action<String>> expression1 = d => d.ToUpper();
var test = cache.ContainsKey(GenerateKey(expression1));
Console.WriteLine(test);
}
public static string GenerateKey(Expression<Action<String>> expr)
{
ParameterExpression newParam = Expression.Parameter(expr.Parameters[0].Type);
Expression newExprBody = Expression.Call(newParam, ((MethodCallExpression)expr.Body).Method);
Expression<Action<String>> newExpr = Expression.Lambda<Action<String>>(newExprBody, newParam);
return newExpr.ToString();
}
Replace a parameter in an expression with a constant
ExpressionVisitor
is your friend:
static void Main()
{
Expression<Func<int, int, bool>> before = (x, y) => x * 2 == y + 1;
var after = ReplaceParameter(before, 3);
Console.WriteLine(after);
}
public static Expression<Func<TElement, bool>> ReplaceParameter<TElement>
(
Expression<Func<TElement, TElement, bool>> inputExpression,
TElement element
)
{
var replacer = new Replacer(inputExpression.Parameters[0],
Expression.Constant(element, typeof(TElement)));
var body = replacer.Visit(inputExpression.Body);
return Expression.Lambda<Func<TElement, bool>>(body,
inputExpression.Parameters[1]);
}
class Replacer : ExpressionVisitor
{
private readonly Expression _from, _to;
public Replacer(Expression from, Expression to)
{
_from = from;
_to = to;
}
public override Expression Visit(Expression node)
=> node == _from ? _to : base.Visit(node);
}
Note that this does not automatically collapse pure constant expressions, i.e. the code shown results in:
y => ((3 * 2) == (y + 1))
You could however, if you wanted, try looking for BinaryExpression
that only has ConstantExpression
as inputs, and evaluate the node directly, again inside Replacer
.
Changing parameter name in a LambdaExpression just for display
It's quick and dirty, but assuming you're using .NET 4.0 you could create the following:
public class PredicateRewriter
{
public static Expression<Predicate<T>> Rewrite<T>(Expression<Predicate<T>> exp, string newParamName)
{
var param = Expression.Parameter(exp.Parameters[0].Type, newParamName);
var newExpression = new PredicateRewriterVisitor(param).Visit(exp);
return (Expression<Predicate<T>>) newExpression;
}
private class PredicateRewriterVisitor : ExpressionVisitor
{
private readonly ParameterExpression _parameterExpression;
public PredicateRewriterVisitor(ParameterExpression parameterExpression)
{
_parameterExpression = parameterExpression;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return _parameterExpression;
}
}
}
And then use it as follows:
var newExp = PredicateRewriter.Rewrite(exp, "b");
newExp.ToString(); // returns "b => (b.First() == 0)" in your case
How to use Lambda expression to replace string parameter
public IEnumerable<Show> ListShows(Func<Show, string> stringFromShow)
{
}
Within that method, use
string str = stringFromShow(show);
Related Topics
How to Share Data Between Forms
How to Install a Certificate into the Local MAChine Store Programmatically Using C#
Generic Way to Check If Entity Exists in Entity Framework
Use of SQLparameter in SQL Like Clause Not Working
Am I Misunderstanding Linq to SQL .Asenumerable()
Explicitly Cast Generic Type Parameters to Any Interface
Save Settings in a .Net Winforms Application
How to Remove Accents on a String
Why Is Only the UI Thread Allowed to Modify the Ui
Split String, Convert Tolist<Int>() in One Line
What's the Difference Between Anonymous Methods (C# 2.0) and Lambda Expressions (C# 3.0)
How to Seed an Admin User in Ef Core 2.1.0
If Int32 Is Just an Alias for Int, How Can the Int32 Class Use an Int