(based on an email conversation, now recorded for information sharing) I have two models used at different layers:
public class TestDTO {
public int CustomerID { get; set; }
}
//...
public class Test {
public int CustomerID { get; set; }
}
and a lambda in terms of my DTO layer:
Expression<Func<TestDTO, bool>> fc1 =
(TestDTO c1) => c1.CustomerID <= 100 && c1.CustomerID >= 10;
How can I convert that lambda (in the general case) to talking about the other model:
Expression<Func<Test, bool>> fc2 = {insert magic here, based on fc1}
(obviously, we're after the same test-condition, but using the Test
type)
?
The difference between a statement and an expression lambda is that the statement lambda has a statement block on the right side of the lambda operator, whereas the expression lambda has only an expression (no return statement or curly braces, for example).
You can't replace the lambda input -> getValueProvider(). apply(input). getValue() with a method reference without changing the semantics. A method reference replace a single method invocation, so it can't simply replace a lambda expression consisting of more than one method invocation.
The body of a statement lambda can consist of any number of statements; however, in practice there are typically no more than two or three.
To do that, you'll have to rebuild the expression-tree completely; the parameters will need re-mapping, and all member-access that is now talking to different types will need to be reapplied. Fortunately, a lot of this is made easier by the ExpressionVisitor
class; for example (doing it all in the general case, not just the Func<T,bool>
predicate usage):
class TypeConversionVisitor : ExpressionVisitor
{
private readonly Dictionary<Expression, Expression> parameterMap;
public TypeConversionVisitor(
Dictionary<Expression, Expression> parameterMap)
{
this.parameterMap = parameterMap;
}
protected override Expression VisitParameter(ParameterExpression node)
{
// re-map the parameter
Expression found;
if(!parameterMap.TryGetValue(node, out found))
found = base.VisitParameter(node);
return found;
}
protected override Expression VisitMember(MemberExpression node)
{
// re-perform any member-binding
var expr = Visit(node.Expression);
if (expr.Type != node.Type)
{
MemberInfo newMember = expr.Type.GetMember(node.Member.Name)
.Single();
return Expression.MakeMemberAccess(expr, newMember);
}
return base.VisitMember(node);
}
}
Here, we pass in a dictionary of parameters to re-map, applying that in VisitParameter
. We also, in VisitMember
, check to see if we've switched type (which can happen if Visit
involves a ParameterExpression
or another MemberExpression
, at any point): if we have, we'll try and find another member of the same name.
Next, we need a general purpose lambda-conversion rewriter method:
// allows extension to other signatures later...
private static Expression<TTo> ConvertImpl<TFrom, TTo>(Expression<TFrom> from)
where TFrom : class
where TTo : class
{
// figure out which types are different in the function-signature
var fromTypes = from.Type.GetGenericArguments();
var toTypes = typeof(TTo).GetGenericArguments();
if (fromTypes.Length != toTypes.Length)
throw new NotSupportedException(
"Incompatible lambda function-type signatures");
Dictionary<Type, Type> typeMap = new Dictionary<Type,Type>();
for (int i = 0; i < fromTypes.Length; i++)
{
if (fromTypes[i] != toTypes[i])
typeMap[fromTypes[i]] = toTypes[i];
}
// re-map all parameters that involve different types
Dictionary<Expression, Expression> parameterMap
= new Dictionary<Expression, Expression>();
ParameterExpression[] newParams =
new ParameterExpression[from.Parameters.Count];
for (int i = 0; i < newParams.Length; i++)
{
Type newType;
if(typeMap.TryGetValue(from.Parameters[i].Type, out newType))
{
parameterMap[from.Parameters[i]] = newParams[i] =
Expression.Parameter(newType, from.Parameters[i].Name);
}
else
{
newParams[i] = from.Parameters[i];
}
}
// rebuild the lambda
var body = new TypeConversionVisitor(parameterMap).Visit(from.Body);
return Expression.Lambda<TTo>(body, newParams);
}
This takes an arbitrary Expression<TFrom>
, and a TTo
, converting it to an Expression<TTo>
, by:
TFrom
/ TTo
Then, putting it all together and exposing our extension method:
public static class Helpers {
public static Expression<Func<TTo, bool>> Convert<TFrom, TTo>(
this Expression<Func<TFrom, bool>> from)
{
return ConvertImpl<Func<TFrom, bool>, Func<TTo, bool>>(from);
}
// insert from above: ConvertImpl
// insert from above: TypeConversionVisitor
}
et voila; a general-purpose lambda conversion routine, with a specific implementation of:
Expression<Func<Test, bool>> fc2 = fc1.Convert<TestDTO, Test>();
You could use AutoMapper (no expression tree):
Mapper.CreateMap<Test, TestDTO>();
...
Func<TestDTO, bool> fc1 =
(TestDTO c1) => c1.CustomerID <= 100 && c1.CustomerID >= 10;
Func<Test, bool> fc2 =
(Test t) => fc1(Mapper.Map<Test, TestDTO>(t));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With