【问题标题】:Replace parameter type in lambda expression替换 lambda 表达式中的参数类型
【发布时间】:2023-04-05 05:39:02
【问题描述】:

我正在尝试将 lambda 表达式中的参数类型从一种类型替换为另一种类型。

我在 stackoverflow 上找到了其他答案,即this one,但我没有找到他们的运气。

想象一下,您有一个域对象和一个可以从中检索域对象的存储库。

但是存储库必须处理自己的数据传输对象,然后映射和返回域对象:

ColorDto.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();
    }
}

正如您所见,这不起作用,因为谓词用于域模型,并且存储库中的 Collection 是数据传输对象的集合。

我尝试使用ExpressionVisitor 来执行此操作,但无法弄清楚如何仅更改ParameterExpression 的类型而不会引发异常,例如:

测试场景

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);
    }
}

最后是例外:

System.ArgumentException:属性“System.String Name”不是 为“ColorDto”类型定义

希望有人能提供帮助。

编辑:这是dotnetfiddle

还是不行。

编辑:这是一个有效的dotnetfiddle

谢谢Eli Arbel

【问题讨论】:

    标签: c# lambda expressionvisitor


    【解决方案1】:

    你需要做一些事情才能让它工作:

    • Expression.Lambda 以及它们出现在正文中的任何位置替换参数实例 - 并为两者使用相同的实例。
    • 更改 lambda 的委托类型。
    • 替换属性表达式。

    这是代码,添加了泛型:

    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);
        }
    }
    

    【讨论】:

    • 我已经调整了 VisitMember 覆盖以使用 MakeMemberAccess:dotnetfiddle.net/HlZgPX
    • @Eli Arbel 感谢您的代码,我能够在 Expression> 中转换 Expression> 但如果我的源表达式是“x = > x.MyProp == 2" 我得到一个运行时错误 "ArgumentException: 实例属性 'MyProp' 未定义为类型 'System.Object' (参数 'propertyName')";如何在转换期间添加演员表?谢谢
    【解决方案2】:

    为每种类型单独定义属性。

    发生该错误是因为您无法从 ColourDto 类型的值中获取由 DomainColour 定义的属性的值。

    您需要访问每个使用该参数的MemberExpression,并返回一个使用新类型的该属性的新MemberExpression

    【讨论】:

    • 你有上述场景的例子吗?
    猜你喜欢
    • 2011-01-07
    • 1970-01-01
    • 2017-01-15
    • 1970-01-01
    • 1970-01-01
    • 2016-11-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多