2

I was generating a dynamic expression on CustomType based on some parameters. Code looks like this:

ParameterExpression parameter = Expression.Parameter(typeof(CustomType), "x");
MemberExpression idMember = Expression.Property(parameter, "CustomProperty");

When I changed from type CustomType to interface ICustomType it stopped working by throwing an error 'Instance property 'CustomProperty' is not defined for type 'ICustomType'' . How to fix it?

user2820173
  • 308
  • 2
  • 13
  • 2
    Welcome to Stack Overflow. Could you give more details about exactly what you mean by "it stopped working"? A [mcve] would be really helpful here. – Jon Skeet Jan 09 '19 at 13:56
  • Why would a parameter be enumerable? It is unusual for a parameter to be a list objects. – jdweng Jan 09 '19 at 14:00
  • @jdweng I have IQueryable, which represents a query on the database. – user2820173 Jan 09 '19 at 14:06
  • I think I got error, because I've used interface as IQueryable customFunction(), not as IQueryable customFunction() where T:class, ICustomClass – user2820173 Jan 09 '19 at 15:19

2 Answers2

2

Without a minimum verifiable example, we cannot be sure what the problem is, but from your example code, I've put together the following:

interface ICustomType
{
    int CustomProperty { get; set; }
}

class CustomType : ICustomType
{
    public int CustomProperty { get; set; }
}

Now, when I call your example code, everything works as expected

ParameterExpression parameter = Expression.Parameter(typeof(CustomType), "x");
MemberExpression idMember = Expression.Property(parameter, "CustomProperty");

Also, when I change the type to ICustomType, it still works as expected.

ParameterExpression parameter = Expression.Parameter(typeof(ICustomType), "x");
MemberExpression idMember = Expression.Property(parameter, "CustomProperty");

However, if I remove the declaration of CustomProperty from ICustomType, I get the following error:

Instance property 'CustomProperty' is not defined for type 'ICustomType'

So, this leads me to believe that your interface does not include a declaration for CustomProperty. If you add it to your interface, your code should then work.

Doctor Jones
  • 21,196
  • 13
  • 77
  • 99
0

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.

l33t
  • 18,692
  • 16
  • 103
  • 180