如何将表达式编译为实际结果?

Posted

技术标签:

【中文标题】如何将表达式编译为实际结果?【英文标题】:How to compile an Expression down to the actual result? 【发布时间】:2011-10-23 11:16:30 【问题描述】:

我正在使用表达式围绕 Web 服务调用构建 API,以允许开发人员指定查询并让 ExpressionVisitor 将表达式转换为查询字符串。请求是带有特定元素的 XML,其中包含查询字符串。

例如,我可以做这样的事情来检索银行名称为 Bank 1 或 Bank 2 的所有支票账户:

"银行名称 = '银行 1' 或银行名称 = '银行 2'"

Web 服务可以处理复杂得多的查询,但我现在就坚持下去。

所以我有一个类 CheckingAccount:


[IntacctObject("checkingaccount")]
public class CheckingAccount : Entity

    [IntacctElement("bankaccountid")]
    public string Id  get; set; 

    [IntacctElement("bankname")]
    public string BankName  get; set; 

还有一个 ExpressionVisitor,其主要功能是像这样转换表达式:

表达式> 表达式 = ca => ca.BankName == "银行 1" || ca.BankName == "银行 2"

进入查询:“bankname = 'Bank 1' or bankname = 'Bank 2'”

这并不难。当我引入局部变量时,事情就开始崩溃了:


var account = new CheckingAccount  BankName = "Bank 1" ;
string bankName = "Bank 2";

Expression> expression = ca => ca.BankName == account.BankName || ca.BankName == bankName;

我知道如何处理一个简单的局部变量(即字符串 bankName = "Bank 2"),但处理另一个类型(var account = new CheckingAccount BankName = "Bank 1" )要复杂得多.

归根结底,这些都是我现在需要弄清楚如何处理的大问题。我知道还有更复杂的场景,但目前我并不太关心这些场景。

这是我的表达式访问者(请注意 CreateFilter 方法的通用约束):


internal class IntacctWebService30ExpressionVisitor : ExpressionVisitor

    private readonly List _Filters = new List();
    private IntacctWebServicev30SimpleQueryFilter _CurrentSimpleFilter;
    private IntacctWebServicev30ComplexQueryFilter _CurrentComplexFilter;
    private MemberExpression _CurrentMemberExpression;

    public string CreateFilter(Expression> expression) where TEntity : Entity
    

        Visit(expression);

        string filter = string.Join(string.Empty, _Filters.Select(f => f.ToString()).ToArray());
        return filter;
    

    protected override Expression VisitBinary(BinaryExpression node)
    
        switch (node.NodeType)
        
            case ExpressionType.AndAlso:
            case ExpressionType.OrElse:
                _CurrentComplexFilter = new IntacctWebServicev30ComplexQueryFilter  ExpressionType = node.NodeType ;
                break;
            case ExpressionType.Equal:
            case ExpressionType.GreaterThan:
            case ExpressionType.GreaterThanOrEqual:
            case ExpressionType.LessThan:
            case ExpressionType.LessThanOrEqual:
            case ExpressionType.NotEqual:
                _CurrentSimpleFilter = new IntacctWebServicev30SimpleQueryFilter  ExpressionType = node.NodeType ;
                break;
        

        return base.VisitBinary(node);
    

    protected override Expression VisitMember(MemberExpression node)
    
        var attr = node.Member.GetAttribute();
        if (attr != null)
            _CurrentSimpleFilter.FieldName = attr.FieldName;
        else
            _CurrentMemberExpression = node;

        return base.VisitMember(node);
    

    protected override Expression VisitConstant(ConstantExpression node)
    
        object value = Expression.Lambda>(node).Compile().Invoke();

        string fieldValue = extraxtFieldValue(value, node);

        _CurrentSimpleFilter.FieldValue = fieldValue;

        if (_CurrentComplexFilter != null)
        
            if (_CurrentComplexFilter.Left == null)
            
                _CurrentComplexFilter.Left = _CurrentSimpleFilter;
            
            else if (_CurrentComplexFilter.Right == null)
            
                _CurrentComplexFilter.Right = _CurrentSimpleFilter;
                _Filters.Add(_CurrentComplexFilter);
            
            else
                throw new InvalidOperationException();
        
        else
        
            _Filters.Add(_CurrentSimpleFilter);
        

        return base.VisitConstant(node);
    

    private string extraxtFieldValue(object value)
    
        string fieldValue;
        if (value is DateTime)
            fieldValue = ((DateTime)value).ToString("yyyy-MM-dd");
        else if (value is string)
            fieldValue = value.ToString();
        else if (value.GetType().IsEnum)
        
            throw new NotImplementedException();
        
        else
        
            // Not sure if this is the best way to do this or not but can't figure out how
            // else to get a variable value.

            // If we are here then we are dealing with a property, field, or variable.
            // This means we must extract the value from the object.
            // In order to do this we will rely on _CurrentMemberExpression
            if (_CurrentMemberExpression.Member.MemberType == MemberTypes.Property)
            
                fieldValue = value.GetType().GetProperty(_CurrentMemberExpression.Member.Name).GetValue(value, null).ToString();
            
            else if (_CurrentMemberExpression.Member.MemberType == MemberTypes.Field)
            
                fieldValue = value.GetType().GetFields().First().GetValue(value).ToString();
            
            else
            
                throw new InvalidOperationException();
            
        

        return fieldValue;
    

如果您需要更多详细信息,请告诉我......

谢谢

【问题讨论】:

您的过滤器是否始终是二进制操作左侧的 Entity.Property? 它们将是属性或字段。 我看到您接受了我对您的其他问题的回答,我的回答是否足够清晰,您可以将 QueryFilter 类集成到其中? 直到我发布这个问题后我才真正看到这一点。它似乎确实有效。奇怪的是,我最初所做的(未在问题中发布)不起作用。最初我正在尝试一些非常相似的东西:Expression.Lambda>(constantExpressionNode, typeof(object)).Compile().Invoke(). 不起作用的原因是因为 constantExpressionNode 并不总是返回“对象”类型的表达式。类型将与字段成员相同,可以是字符串/整数/日期时间等。这就是我使用通用“Expression.Lambda”和通用委托的原因 【参考方案1】:

看看an article about this very issue from Matt Warren。他为一个完全做到这一点的类提供了源代码,并解释了它是如何做到的。它也包含在his IQToolkit 中。

【讨论】:

【参考方案2】:

如果您有兴趣使用开源第三方库为您执行此操作,可以查看Expression Tree Serialization。我相信它可以满足您的需求。

【讨论】:

查看序列化程序如何将任何 LINQ 表达式转换为 XElement,然后可以通过网络将其转换为 xml 字符串,这似乎正是 op 想要做的。如果您能指出缺少的内容,我将不胜感激。 我读问题的方式,他想把表达式转换成非常具体的形式(例子是"bankname = 'Bank 1' or bankname = 'Bank 2'"),而不是一些XML。 " 请求是带有包含查询字符串的特定元素的 XML。"在我看来,他似乎正试图将查询转换为序列化的 xml 字符串,这就是我指向的库所做的。由于它会序列化和反序列化,并且 Expression 有一个 .ToString() 方法可以使它更纯文本,这是我能想到的最接近的。

以上是关于如何将表达式编译为实际结果?的主要内容,如果未能解决你的问题,请参考以下文章

基于 DFA 的 Java 正则表达式引擎与 Capture

正则表达式

re模块

python 优雅的使用正则表达式 ~ 2

re模块的简单使用

编译器:如何解析函数调用和函数定义