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
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.
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.
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.
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.
You need to do a few things for this to work:
Expression.Lambda
and anywhere they appear in the body - and use the same instance for both.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);
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With