A common task when working with expressions is to replace certain nodes with other nodes. For instance, you can replace ParameterExpression as in this answer. I believe the OP used a similar parameter replacer, and forgot to also replace MemberExpression.
If you replace parameters of an expression, the original MemberExpression might not the new parameter type. E.g. a member expression for CustomType.CustomProperty will not be able to handle ICustomType.CustomProperty.
If my theory is correct, the OP must replace some MemberExpression instances too. The following expression visitor will do the trick:
public class ParameterReplacerVisitor : ExpressionVisitor
{
private readonly Type newType;
private Dictionary<ParameterExpression, ParameterExpression> parametersToReplace;
public ParameterReplacerVisitor(Type newType)
{
this.newType = newType;
}
public LambdaExpression Convert(LambdaExpression expression)
{
parametersToReplace = expression.Parameters
.Where(p => ShouldReplace(p.Type))
.ToDictionary(p => p, p => Expression.Parameter(newType, p.Name));
return (LambdaExpression)Visit(expression);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
var parameters = node.Parameters.Select(GetNewParameter);
return Expression.Lambda(Visit(node.Body), parameters);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return base.VisitParameter(GetNewParameter(node));
}
protected override Expression VisitMember(MemberExpression node)
{
if (ShouldReplace(node.Member.DeclaringType))
{
var targetProperty = GetNewProperty(node.Member);
node = Expression.MakeMemberAccess(Visit(node.Expression), targetProperty);
}
return base.VisitMember(node);
}
private MemberInfo GetNewProperty(MemberInfo member)
{
return newType.GetProperty(member.Name) ?? throw new ArgumentException(
$"Property '{member.Name}' is not defined for type '{newType.Name}'"
);
}
private bool ShouldReplace(Type type) => newType.IsAssignableFrom(type);
private ParameterExpression GetNewParameter(ParameterExpression parameter)
{
parametersToReplace.TryGetValue(parameter, out var newParameter);
return newParameter ?? parameter;
}
}
Example
Expression<Func<Derived, string>> derived = t => t.A;
var lessDerived = derived.ToLessDerived<Derived, IBase, string>();
var d = lessDerived.Compile();
var result = d.Invoke(new Base());
And the extension method:
public static Expression<Func<TLess, TValue>> ToLessDerived<TClass, TLess, TValue>(this Expression<Func<TClass, TValue>> expression)
{
var visitor = new ParameterReplacerVisitor(typeof(TLess));
return (Expression<Func<TLess, TValue>>)visitor.Convert(expression);
}
For me, this solved the exact same type of error as the OP asked about.