Reputation: 211
I have two unrelated classes. One is exposed as API, and the other is used internally by 3rd party API.
Entity is exposed from our API, while EntityProvider is from the 3rd party assembly.
class Entity
{
public A { get; set; }
}
class EntityProvider
{
public A { get; set; }
}
Consumers of our API will provide predicates of the form Expression <Func<Entity, bool>>
and I need to modify it to Expression <Func<EntityProvider, bool>>
so that I can pass the same to internal 3rd party assembly.
Please help with this conversion.
Upvotes: 5
Views: 2667
Reputation: 750
Since Expressions in .NET are immutable, the only way to do this is to rebuild the whole expression. To do this usually involves inheriting from the ExpressionVisitor class. Depending on the complexity of the expressions you have to convert this could be quite complicated.
This is a simple example of a visitor that will work with simple expressions( like x=>x.Someproperty == somevalue ). It's just an example to get you started and it's in no way finished or tested(it won't handle method calls in the expression for example)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
//Type from which to convert
public class A
{
public int Property1 { get; set; }
public int Property2 { get; set; }
}
//Type to which we want the Expression converted
public class B
{
public int Property1 { get; set; }
public int Property2 { get; set; }
}
class Program
{
static void Main(string[] args)
{
//the expression we want to convert expresion
Expression<Func<A, bool>> expA = x => x.Property1 == 6 && x.Property2 == 3;
var visitor = new ParameterTypeVisitor<A,B>(expA);
var expB = visitor.Convert();
var b = new B() { Property1 = 6, Property2 = 3 };
//try the converted expression
var result = expB.Compile().Invoke(b);
}
}
public class ParameterTypeVisitor<TFrom,TTo> : ExpressionVisitor
{
private Dictionary<string, ParameterExpression> convertedParameters;
private Expression<Func<TFrom, bool>> expression;
public ParameterTypeVisitor(Expression<Func<TFrom,bool>> expresionToConvert )
{
//for each parameter in the original expression creates a new parameter with the same name but with changed type
convertedParameters = expresionToConvert.Parameters
.ToDictionary(
x => x.Name,
x => Expression.Parameter(typeof (TTo), x.Name)
);
expression = expresionToConvert;
}
public Expression<Func<TTo,bool>> Convert()
{
return (Expression<Func<TTo, bool>>)Visit(expression);
}
//handles Properties and Fields accessors
protected override Expression VisitMember(MemberExpression node)
{
//we want to replace only the nodes of type TFrom
//so we can handle expressions of the form x=> x.Property.SubProperty
//in the expression x=> x.Property1 == 6 && x.Property2 == 3
//this replaces ^^^^^^^^^^^ ^^^^^^^^^^^
if (node.Member.DeclaringType == typeof(TFrom))
{
//gets the memberinfo from type TTo that matches the member of type TFrom
var memeberInfo = typeof (TTo).GetMember(node.Member.Name).First();
//this will actually call the VisitParameter method in this class
var newExp = Visit(node.Expression);
return Expression.MakeMemberAccess(newExp, memeberInfo);
}
else
{
return base.VisitMember(node);
}
}
// this will be called where ever we have a reference to a parameter in the expression
// for ex. in the expression x=> x.Property1 == 6 && x.Property2 == 3
// this will be called twice ^ ^
protected override Expression VisitParameter(ParameterExpression node)
{
var newParameter = convertedParameters[node.Name];
return newParameter;
}
//this will be the first Visit method to be called
//since we're converting LamdaExpressions
protected override Expression VisitLambda<T>(Expression<T> node)
{
//visit the body of the lambda, this will Traverse the ExpressionTree
//and recursively replace parts of the expression we for which we have matching Visit methods
var newExp = Visit(node.Body);
//this will create the new expression
return Expression.Lambda(newExp,convertedParameters.Select(x=>x.Value));
}
}
Upvotes: 7