As written by Evk
you'll need an ExpressionVisitor.
For little more complex expressions than the ones supported by Evk, like multiple parameters, use of the constructor and of member initializer list (new { Prop1 = true }
):
public class TypeReplacer : ExpressionVisitor
{
public readonly Dictionary<Type, Type> Conversions = new Dictionary<Type, Type>();
private readonly Dictionary<Expression, Expression> ParameterConversions = new Dictionary<Expression, Expression>();
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
{
Type to;
if (Conversions.TryGetValue(node.Member.DeclaringType, out to))
{
var member = ConvertMember(node.Member, to);
node = Expression.Bind(member, node.Expression);
}
return base.VisitMemberAssignment(node);
}
public override Expression Visit(Expression node)
{
if (node.NodeType == ExpressionType.Lambda)
{
var lambda = (LambdaExpression)node;
var parameters = lambda.Parameters.ToArray();
for (int i = 0; i < parameters.Length; i++)
{
ParameterExpression parameter = parameters[i];
Type to;
if (Conversions.TryGetValue(parameter.Type, out to))
{
var oldParameter = parameter;
parameter = Expression.Parameter(to, parameter.Name);
ParameterConversions.Add(oldParameter, parameter);
}
}
var body = base.Visit(lambda.Body);
for (int i = 0; i < parameters.Length; i++)
{
var parameter = (ParameterExpression)base.Visit(parameters[i]);
parameters[i] = parameter;
}
// Handling of the delegate type
var arguments = node.Type.GetGenericArguments();
{
Type to;
for (int i = 0; i < arguments.Length; i++)
{
if (Conversions.TryGetValue(arguments[i], out to))
{
arguments[i] = to;
}
}
}
var delegateType = node.Type.GetGenericTypeDefinition().MakeGenericType(arguments);
node = Expression.Lambda(delegateType, body, parameters);
return node;
}
return base.Visit(node);
}
protected override Expression VisitConstant(ConstantExpression node)
{
Type to;
if (Conversions.TryGetValue(node.Type, out to))
{
node = Expression.Constant(node.Value, to);
}
return base.VisitConstant(node);
}
protected override Expression VisitNew(NewExpression node)
{
Type to;
if (Conversions.TryGetValue(node.Type, out to))
{
var constructor = node.Constructor;
BindingFlags bf = (constructor.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic) |
BindingFlags.Instance;
var parameters = constructor.GetParameters();
var types = Array.ConvertAll(parameters, x => x.ParameterType);
var constructor2 = to.GetConstructor(bf, null, types, null);
if (node.Members != null)
{
// Shouldn't happen. node.Members != null with anonymous types
IEnumerable<MemberInfo> members = node.Members.Select(x => ConvertMember(x, to));
node = Expression.New(constructor2, node.Arguments, members);
}
else
{
node = Expression.New(constructor2, node.Arguments);
}
}
return base.VisitNew(node);
}
protected override Expression VisitMember(MemberExpression node)
{
Type to = null;
Expression expression = null;
if (node.Expression != null)
{
if (ParameterConversions.TryGetValue(node.Expression, out expression))
{
to = expression.Type;
}
}
if (to != null || (node.Expression == null && Conversions.TryGetValue(node.Member.DeclaringType, out to)))
{
MemberInfo member = ConvertMember(node.Member, to);
node = Expression.MakeMemberAccess(expression, member);
}
return base.VisitMember(node);
}
protected override Expression VisitParameter(ParameterExpression node)
{
Expression to;
if (ParameterConversions.TryGetValue(node, out to))
{
node = (ParameterExpression)to;
}
return base.VisitParameter(node);
}
// Conversion of method/property/field accessor (supported indexers)
private static MemberInfo ConvertMember(MemberInfo member, Type to)
{
switch (member.MemberType)
{
case MemberTypes.Field:
{
var field = (FieldInfo)member;
BindingFlags bf = (field.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic) |
(field.IsStatic ? BindingFlags.Static : BindingFlags.Instance);
var field2 = to.GetField(member.Name, bf);
return field2;
}
case MemberTypes.Property:
{
var prop = (PropertyInfo)member;
var method = prop.GetMethod ?? prop.SetMethod;
BindingFlags bf = (method.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic) |
(method.IsStatic ? BindingFlags.Static : BindingFlags.Instance);
var indexes = prop.GetIndexParameters();
var types = Array.ConvertAll(indexes, x => x.ParameterType);
var property2 = to.GetProperty(member.Name, bf, null, prop.PropertyType, types, null);
return property2;
}
case MemberTypes.Method:
{
var method = (MethodInfo)member;
BindingFlags bf = (method.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic) |
(method.IsStatic ? BindingFlags.Static : BindingFlags.Instance);
var parameters = method.GetParameters();
var types = Array.ConvertAll(parameters, x => x.ParameterType);
var method2 = to.GetMethod(member.Name, bf, null, types, null);
return method2;
}
default:
throw new NotSupportedException(member.MemberType.ToString());
}
}
}
And you use like this:
Expression<Func<Class1a, Class1b, bool>> exp1 = (x, y) => x.Prop1;
var visitor = new TypeReplacer();
visitor.Conversions.Add(typeof(Class1a), typeof(Class2a));
visitor.Conversions.Add(typeof(Class1b), typeof(Class2b));
var result = (Expression<Func<Class2a, Class2b, bool>>)visitor.Visit(exp1);
Note that this code shows how much pain can be doing this conversion in the most complex cases... This code isn't complete... There are many corner cases not covered here! (for example this isn't supported: x.SomeMethod(new ClassOriginal()) -> x.SomeMethod(new ClassConverted()
.
Added support for:
x => x;
x => null;
(they needed special handling)
ExpressionVisitor
... It is a little pain, because you'll have to replace nearly all the expression... – Devolve