重写表达式<t>

时间:2019-02-01 09:05:46

标签: c# lambda

是否可以动态重写Expression<T>并将T的元素替换为另一种类型?

例如,在以下情况下,将DocumentTypeA替换为DocumentTypeB

  
      
  • Expression<Func<DocumentTypeA, bool>> expression = m => m.Details.Id == "an-id"
  •   
  • Expression<Func<DocumentTypeA, bool>> expression = m => m.Details.Id == "an-id" && m.AnInt == 42
  •   
  • Expression<Func<DocumentTypeA, bool>> expression = m => m.AString == "I'm a string"
  •   

我需要决定在运行时使用哪种类型,而不是在编译时使用。

还值得注意的是,DocumentTypeADocumentTypeB彼此不相关,除了它们的属性相同之外。

最终结果将是重新处理它们,使它们现在看起来像

  
      
  • Expression<Func<DocumentTypeB, bool>> expression = m => m.Details.Id == "an-id"
  •   
  • Expression<Func<DocumentTypeB, bool>> expression = m => m.Details.Id == "an-id" && m.AnInt == 42
  •   
  • Expression<Func<DocumentTypeB, bool>> expression = m => m.AString == "I'm a string"
  •   

因此,表达式的实际比较部分保持不变,只有顶级类型发生了变化。

2 个答案:

答案 0 :(得分:3)

您可以使用ExpressionVisitor来替换类型。

class ParameterRewriter<T, U> : ExpressionVisitor
{
    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node.Type.Equals(typeof(T)))
        {
            return Expression.Parameter(typeof(U), node.Name);
        }

        return base.VisitParameter(node);
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Expression is ParameterExpression paramExp && paramExp.Type.Equals(typeof(T)))
        {
            return Expression.MakeMemberAccess(
                Expression.Parameter(typeof(U), paramExp.Name),
                typeof(U).GetMember(node.Member.Name).Single());
        }

        return base.VisitMember(node);
    }

    protected override Expression VisitLambda<L>(Expression<L> node)
    {
        var parameters = node.Parameters.ToList();
        var found = false;

        for (var i = 0; i < parameters.Count; i++)
        {
            if (parameters[i].Type.Equals(typeof(T)))
            {
                parameters[i] = Expression.Parameter(typeof(U), parameters[i].Name);
                found = true;
            }
        }

        if (found)
        {
            return Expression.Lambda(node.Body, parameters);
        }

        return base.VisitLambda(node);
    }
}

在这种情况下,创建一个实例new ParameterRewriter<DocumentTypeA, DocumentTypeB>()并访问原始表达式树,您将获得所需的内容。扩展方法可能更具可读性:

public static class ExpressionExtensions
{
    public static Expression<Func<U, R>> RewriteParameter<T, U, R>(this Expression<Func<T, R>> expression)
    {
        var rewriter = new ParameterRewriter<T, U>();
        return (Expression<Func<U, R>>)rewriter.Visit(expression);
    }
}

用法很简单:

Expression<Func<A, bool>> expA = x => x.Id == 1;
Expression<Func<B, bool>> expB = expA.RewriteParameter<A, B, bool>();

答案 1 :(得分:1)

使用两个类都继承并包含两个类相同属性的接口,例如

interface IDocumentTypes
{
    string AString { get; set; } //indicates that both classes need to implement this
    //etc...
}

class DocumentTypeA : IDocumentTypes
{
    //your class
}

然后,您的两个类都可以在实现接口Expression时使用IDocumentTypes,并且仍应使用强类型。除了实现接口中定义的属性/功能外,这些类不需要有任何共同点。

相关问题