Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace parameter type in lambda expression

I am trying to replace the parameter type in a lambda expression from one type to another.

I have found other answers on stackoverflow i.e. this one but I have had no luck with them.

Imagine for a second you have a domain object and a repository from which you can retrieve the domain object.

however the repository has to deal with its own Data transfer objects and then map and return domain objects:

ColourDto.cs

public class DtoColour {

    public DtoColour(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
}

DomainColour.cs

public class DomainColour {

    public DomainColour(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
}

Repository.cs

public class ColourRepository {
    ...
    public IEnumerable<DomainColour> GetWhere(Expression<Func<DomainColour, bool>> predicate)
    {
        // Context.Colours is of type ColourDto
        return Context.Colours.Where(predicate).Map().ToList();
    }
}

As you can see this will not work as the predicate is for the domain model and the Collection inside the repository is a collection of Data transfer objects.

I have tried to use an ExpressionVisitor to do this but cannot figure out how to just change the type of the ParameterExpression without an exception being thrown for example:

Test scenario

public class ColourRepository {
    ...
    public IEnumerable<DomainColour> GetWhere(Expression<Func<DomainColour, bool>> predicate)
    {
        var visitor = new MyExpressionVisitor();
        var newPredicate = visitor.Visit(predicate) as Expression<Func<ColourDto, bool>>;
        return Context.Colours.Where(newPredicate.Complie()).Map().ToList();
    }
}


public class MyExpressionVisitor : ExpressionVisitor
{
    protected override Expression VisitParameter(ParameterExpression node)
    {
        return Expression.Parameter(typeof(ColourDto), node.Name);
    }
}

finally here is the exception:

System.ArgumentException : Property 'System.String Name' is not defined for type 'ColourDto'

Hope someone can help.

EDIT: Here is a dotnetfiddle

still doesnt work.

Edit: Here is a working dotnetfiddle

Thanks Eli Arbel

like image 632
Jake Aitchison Avatar asked Jul 11 '16 21:07

Jake Aitchison


People also ask

Is parameter types can be inferred in lambda expression?

Type Inference means that the data type of any expression (e.g. method return type or parameter type) can be deduced automatically by the compiler. Groovy language is a good example of programming languages supporting Type Inference. Similarly, Java 8 Lambda expressions also support Type inference.

Can we write parameter less lambda expression?

No, there isn't. Lambda expressions are optimised (in terms of syntax) for the single parameter case. I know that the C# team feels your pain, and have tried to find an alternative.

What is the type of a lambda expression?

The lambda expressions have a very simple, precise syntax and provide flexibility to specify the datatypes for the function parameters. Its return type is a parameter -> expression body to understand the syntax, we can divide it into three parts.

How do you convert this method into a lambda expression?

To convert an anonymous method to a lambda expressionMove to the anonymous method you want to convert. From the Refactor menu of the VisualAid choose To Lambda. Telerik® JustCode™ will replace the anonymous method with a lambda expression.


2 Answers

You need to do a few things for this to work:

  • Replace parameter instance both at the Expression.Lambda and anywhere they appear in the body - and use the same instance for both.
  • Change the lambda's delegate type.
  • Replace the property expressions.

Here's the code, with added generics:

public static Func<TTarget, bool> Convert<TSource, TTarget>(Expression<Func<TSource, bool>> root)
{
    var visitor = new ParameterTypeVisitor<TSource, TTarget>();
    var expression = (Expression<Func<TTarget, bool>>)visitor.Visit(root);
    return expression.Compile();
}

public class ParameterTypeVisitor<TSource, TTarget> : ExpressionVisitor
{
    private ReadOnlyCollection<ParameterExpression> _parameters;

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return _parameters?.FirstOrDefault(p => p.Name == node.Name) ?? 
            (node.Type == typeof(TSource) ? Expression.Parameter(typeof(TTarget), node.Name) : node);
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        _parameters = VisitAndConvert<ParameterExpression>(node.Parameters, "VisitLambda");
        return Expression.Lambda(Visit(node.Body), _parameters);
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Member.DeclaringType == typeof(TSource))
        {
            return Expression.Property(Visit(node.Expression), node.Member.Name);
        }
        return base.VisitMember(node);
    }
}
like image 117
Eli Arbel Avatar answered Oct 08 '22 12:10

Eli Arbel


Properties are defined separately for each type.

That error happens because you can't get the value of a property defined by DomainColour from a value of type ColourDto.

You need to visit every MemberExpression that uses the parameter and return a new MemberExpression that uses that property from the new type.

like image 45
SLaks Avatar answered Oct 08 '22 12:10

SLaks