简体   繁体   English

替换lambda表达式中的参数类型

[英]Replace parameter type in lambda expression

I am trying to replace the parameter type in a lambda expression from one type to another. 我试图将lambda表达式中的参数类型从一种类型替换为另一种类型。

I have found other answers on stackoverflow ie this one but I have had no luck with them. 我在stackoverflow上找到了其他答案,即这个,但我没有运气。

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 ColourDto.cs

public class DtoColour {

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

    public string Name { get; set; }
}

DomainColour.cs DomainColour.cs

public class DomainColour {

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

    public string Name { get; set; }
}

Repository.cs 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. 正如您所看到的,这将不起作用,因为谓词是针对域模型的,而存储库中的Collection是数据传输对象的集合。

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: 我曾尝试使用ExpressionVisitor来执行此操作,但无法弄清楚如何在不抛出异常的情况下更改ParameterExpression的类型,例如:

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' System.ArgumentException:未为类型'ColourDto'定义属性'System.String Name'

Hope someone can help. 希望有人能提供帮助。

EDIT: Here is a dotnetfiddle 编辑:这是一个dotnetfiddle

still doesnt work. 还是不行。

Edit: Here is a working dotnetfiddle 编辑:这是一个工作的dotnetfiddle

Thanks Eli Arbel 谢谢Eli Arbel

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. Expression.Lambda和它们出现在正文中的任何位置替换参数实例 - 并对两者使用相同的实例。
  • Change the lambda's delegate type. 更改lambda的委托类型。
  • 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);
    }
}

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 . 发生该错误是因为您无法从ColourDto类型的值获取DomainColour定义的属性的值。

You need to visit every MemberExpression that uses the parameter and return a new MemberExpression that uses that property from the new type. 您需要访问使用该参数的每个MemberExpression ,并返回一个使用新类型的该属性的新MemberExpression

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM