c# lambda 表达式_将C#Lambda表达式转换为通用过滤器描述符和HTTP查询字符串

c# lambda 表达式

介绍 (Introduction)

It is relatively common to build a mechanism for converting business conditions expressed in code to a format usable by other tiers of a solution, be it a database or a Web service, especially in the infrastructure layers of a solution. Either of the two following common scenarios is an example of such case:

建立一种将代码表示的业务条件转换为解决方案的其他层(数据库或Web服务)可用的格式的机制是相对普遍的,尤其是在解决方案的基础结构层中。 以下两种常见方案中的任何一种都是这种情况的示例:

  1. Suppose we want to pass filter conditions from inside a C# client to an HTTP service. These conditions can be sent in a query string collection, but manually constructing query strings through string concatenation, not only does not seem nice and clean, but would highly likely be hard to debug and maintain.

    假设我们想将过滤条件从C#客户端内部传递到HTTP服务。 这些条件可以在查询字符串集合中发送,但是通过字符串串联手动构造查询字符串,不仅看起来不干净,而且很可能难以调试和维护。
  2. There can be times that we might want to translate filter conditions into SQL WHERE clauses without using an ORM tool. Again, constructing SQL WHERE clauses for database queries through manual string manipulation seems error prone and hard to maintain.

    有时候,我们可能希望在不使用ORM工具的情况下将过滤条件转换为SQL WHERE子句。 同样,通过手动字符串操作为数据库查询构造SQL WHERE子句似乎容易出错并且难以维护。

As an elegant tool, "lambda expressions" give a concise and convenient means of describing filter conditions but working with these expressions is not very easy. Luckily, ExpressionVisitor class in System.Linq.Expressions namespace is an excellent tool for inspecting, modifying and translating lambda expressions.

作为一种出色的工具,“ lambda表达式”提供了描述过滤条件的简洁方便的方法,但是使用这些表达式并不是很容易。 幸运的是, System.Linq.Expressions命名空间中的ExpressionVisitor类是检查,修改和翻译lambda表达式的出色工具。

In this article, we mainly use ExpressionVisitor class to propose a solution to the first scenario above.

在本文中,我们主要使用ExpressionVisitor类为上述第一种情况提出解决方案。

背景 (Background)

Before diving into the details, let us have a very brief introduction to the general concept of expressions, then the condition expressions as a more special type, and finally a very short description of ExpressionVisitor class. It will be very short but absolutely necessary, so please skip this section only if you know these subjects beforehand.

在深入探讨细节之前,让我们对表达式一般概念进行非常简单的介绍,然后将条件表达式作为一种更特殊的类型,最后对ExpressionVisitor类进行非常简短的描述。 这将非常简短,但绝对必要,因此,仅在事先了解这些主题的情况下,才跳过此部分。

一般而言,什么是表达式?条件表达式与它们有什么不同? (What Are Expressions in General and How Are Condition Expressions Different From Them?)

Expressions in general represent a delegate or method. An expression itself is not a delegate or method. It represents a delegate or method, i.e., an expression defines a delegate's structure. In .NET platform, we use Expression class to define an expression. However, before defining its delegate's body, it is necessary to define the signature of the delegate it is going to represent. This signature is given to the Expression class via a generic type parameter named TDelegate. Therefore, the form of the expression class is Expression<TDelegate>.

表达式通常表示委托或方法。 表达式本身不是委托或方法。 它表示委托或方法,即,表达式定义了委托的结构。 在.NET平台中,我们使用Expression类定义表达式。 但是,在定义其委托人的主体之前,有必要定义将要代表的委托人的签名。 该签名通过名为TDelegate的通用类型参数提供给Expression类。 因此,表达式类的形式为Expression <TDelegate>

Having this in mind, it is obvious that a condition expression represents a delegate that takes an object of an arbitrary type T as the input and returns a Boolean value. As a result, the delegate of a condition expression will be of type Func<T, bool>, hence Expression<Func<T, bool>> the type of the condition expression.

考虑到这一点,很明显,条件表达式表示一个委托,该委托将任意类型T的对象作为输入并返回布尔值。 结果,条件表达式的委托将是Func<T, bool> ,因此Expression<Func<T, bool>>是条件表达式的类型。

ExpressionVistor如何工作 (How ExpressionVistor Works)

We usually use a lambda expression to define an expression. A lambda expression consists of multiple different expressions combined together. Consider this example lambda:

我们通常使用lambda表达式来定义一个表达式。 Lambda表达式由多个不同的表达式组合在一起。 考虑以下示例lambda:

p => p.Price < 1000 && p.Name.StartsWith("a-string") && !p.OutOfStock

The below figure marks different parts of it:

下图标记了它的不同部分:

Image 1

As you can see, this expression is a combination of some other expressions and operators.

如您所见,此表达式是其他一些表达式和运算符的组合。

Now let us see how ExpressionVisitor treats an expression like the one above. This class implements visitor pattern. Its main method, or entry point, called Visit is a dispatcher that calls several other specialized methods. When an expression is passed to the Visit method, the expression tree is traversed and depending on the type of each node, a specialized method is called to visit (inspect and modify) that node, and its children, if any. Inside each method, if the expression is modified, the modified copy of it will be returned; otherwise, the original expression. Please keep in mind that expressions are immutable and any modification would result in a new instance being built and returned.

现在让我们看看ExpressionVisitor如何像上面的表达式一样对待表达式。 此类实现访客模式 。 它的主要方法(称为“ Visit点”)是一个调度程序,它调用其他几种专门的方法。 将表达式传递给Visit方法时,将遍历表达式树,并根据每个节点的类型,调用专门的方法来访问(检查和修改)该节点及其子节点(如果有)。 在每个方法内部,如果表达式被修改,则将返回其修改后的副本; 否则为原始表达。 请记住,表达式是不可变的,任何修改都会导致生成并返回一个新实例。

In Microsoft’s online documentation for .NET framework 4.8, 35 special visit methods are documented. A few interesting ones that are used in our solution are listed here:

在Microsoft的.NET Framework 4.8 在线文档中, 记录了35种特殊的访问方法。 下面列出了我们的解决方案中使用的一些有趣的方法:

All those 35 variants of visit method are virtual and any class inheriting from ExpressionVisitor should override the necessary ones and implement its own logic. This is how a custom visitor is built.

visit方法的所有这35个变体都是虚拟的,从ExpressionVisitor继承的任何类都应覆盖必需的类并实现其自己的逻辑。 这就是定制访问者的构建方式。

For those readers who might be willing to obtain a good understanding of how our solution works, having at least a minimum familiarity with the following subjects is necessary.

对于那些可能希望很好地理解我们的解决方案工作原理的读者,有必要至少了解以下主题。

    • A general concept behind the lambda expressions that we want to translate

      我们要翻译的lambda表达式背后的一般概念

    Expression Trees (1) & (2)

    表达式树(1)(2)

    • Algorithms used to iterate a tree

      用于迭代树的算法

    Tree Traversals (Inorder, Preorder and Postorder)

    树遍历(顺序,预排序和后排序)

    • A design pattern that is used to parse expression trees

      一种用于解析表达式树的设计模式

    Visitor Design Pattern

    访客设计模式

    • A class provided by Microsoft .NET platform that uses visitor design pattern to expose methods for inspecting, modifying and translating expression trees. We will be using these methods to inspect each node of the interest in the tree and extract needed data from it.

      Microsoft .NET平台提供的类,该类使用访问者设计模式来公开检查,​​修改和翻译表达式树的方法。 我们将使用这些方法来检查树中每个感兴趣的节点,并从中提取所需的数据。

    ExpressionVisitor Class

    ExpressionVisitor类

    • In reverse Polish notation, the operators follow their operands; for instance, to add 3 and 4, one would write "3 4 +" rather than "3 + 4".

      相反的波兰语表示法,运算符遵循其操作数; 例如,将3和4相加,便会写成“ 3 4 +”而不是“ 3 + 4”。

    Reverse Polish Notation (RPN)

    反向波兰语符号(RPN)

大图景 (The Big Picture)

As the below figure shows, we have a FilterBuilder class that takes an expression of type Expression<Func<T, bool>> as the input. This class is the main part of the solution. At the first step, the FilterBuilder examines the input expression and outputs a collection of FilterDescriptors (IEnumerable<FilterDescriptor>). In the next step, a converter, converts this collection of FilterDescriptors to a desired form, e.g., query string key-value pairs to be used in an HTTP request, or a string to be used as a SQL WHERE clause. For every type of conversion, a separate converter is needed.

如下图所示,我们有一个FilterBuilder类,该类以Expression<Func<T, bool>>类型的Expression<Func<T, bool>>作为输入。 此类是解决方案的主要部分。 第一步, FilterBuilder检查输入表达式并输出FilterDescriptor的集合( IEnumerable<FilterDescriptor> )。 在下一步中,转换器将FilterDescriptors此集合转换为所需的形式,例如,要在HTTP请求中使用的查询字符串键/值对,或要用作SQL WHERE子句的字符串。 对于每种类型的转换,都需要一个单独的转换器。

Image 2

One question may arise here: why do we not convert the input expression directly to a query string? Is it necessary to take on the burden of generating FilterDescriptors? Can this extra step be skipped? The answer is if all you need is to generate query strings and no more than that, and if you are not looking for a general solution, you are free to do so. However, this way, you will end up having a very specific ExpressionVisitor suitable for only one type of output. For that goal, a good article is written here. However, what this article tries to do is exactly the opposite: proposing a more general solution.

这里可能会出现一个问题:为什么不将输入表达式直接转换为查询字符串? 是否有必要承担生成FilterDescriptors的负担? 可以跳过此额外步骤吗? 答案是,如果您只需要生成查询字符串,而不仅仅是生成查询字符串,并且如果您不寻找通用解决方案,那么您可以自由地这样做。 但是,通过这种方式,您最终将获得非常特定的ExpressionVisitor ,仅适用于一种类型的输出。 为了这个目标, 这里写了一篇很好的文章。 但是,本文试图做的恰恰相反:提出一个更通用的解决方案。

解决方案 (The Solution)

地基 (Foundations)

At the heart of our solution is the FilterBuilder class, which inherits from ExpressionVisitor. The constructor of this class takes an expression of type Expresion<Func<T, bool>>. This class has a public method named Build that returns a collection of FilterDescriptor objects. FiterDescriptor is defined as follows:

解决方案的核心是FilterBuilder类,该类继承自ExpressionVisitor 。 此类的构造函数采用Expresion<Func<T, bool>>类型的表达式。 此类具有一个名为Buildpublic方法,该方法返回FilterDescriptor对象的集合。 FiterDescriptor定义如下:

public class FilterDescriptor
{
    public FilterDescriptor()
    {
        CompositionOperator = FilterOperator.And;
    }

    private FilterOperator _compositionOperator;

    public FilterOperator CompositionOperator
    {
        get => _compositionOperator;
        set
        {
            if (value != FilterOperator.And && value != FilterOperator.Or)
                throw new ArgumentOutOfRangeException();

            _compositionOperator = value;
        }
    }

    public string FieldName { get; set; }
    public object Value { get; set; }
    public FilterOperator Operator { get; set; }     

    // For demo purposes
    public override string ToString()
    {
        return
            $"{CompositionOperator} {FieldName ?? "FieldName"} {Operator} {Value ?? "Value"}";

    }
}

Type of the FilterOperator property of this class is an enumeration. This property specifies the operator of the filter.

此类的FilterOperator属性的类型是一个枚举。 此属性指定过滤器的运算符。

public enum FilterOperator
{
    NOT_SET,

    // Logical
    And,
    Or,
    Not,

    // Comparison
    Equal,
    NotEqual,
    LessThan,
    LessThanOrEqual,
    GreaterThan,
    GreaterThanOrEqual,

    // String
    StartsWith,
    Contains,
    EndsWith,
    NotStartsWith,
    NotContains,
    NotEndsWith
}

Expressions nodes are not directly converted to FilterDescriptor objects. Instead, each overridden method that visits an expression node, creates an object named token and adds it to a private list. Tokens in this list are arranged according to Reverse Polish Notation (RPN). What is a token? A token encapsulates node data required to build FilterDescriptors. Tokens are defined by classes that inherit from an abstract Token class.

表达式节点不会直接转换为FilterDescriptor对象。 而是,每个访问表达式节点的重写方法,都创建一个名为token的对象,并将其添加到私有列表中。 此列表中的令牌是根据反向波兰符号(RPN)排列的。 什么是代币? 令牌封装了构建FilterDescriptor所需的节点数据。 令牌由继承自抽象Token类的类定义。

public abstract class Token {}

public class BinaryOperatorToken : Token
{
    public FilterOperator Operator { get; set; }

    public BinaryOperatorToken(FilterOperator op)
    {
        Operator = op;
    }

    public override string ToString()
    {
        return "Binary operator token:\t" + Operator.ToString();
    }
}

public class ConstantToken : Token
{
    public object Value { get; set; }

    public ConstantToken(object value)
    {
        Value = value;
    }

    public override string ToString()
    {
        return "Constant token:\t\t" + Value.ToString();
    }
}

public class MemberToken : Token
{
    public Type Type { get; set; }

    public string MemberName { get; set; }

    public MemberToken(string memberName, Type type)
    {
        MemberName = memberName;
        Type = type;
    }

    public override string ToString()
    {
        return "Member token:\t\t" + MemberName;
    }
}

public class MethodCallToken : Token
{
    public string MethodName { get; set; }

    public MethodCallToken(string methodName)
    {
        MethodName = methodName;
    }

    public override string ToString()
    {
        return "Method call token:\t" + MethodName;
    }
}

public class ParameterToken : Token
{
    public string ParameterName { get; set; }
    public Type Type { get; set; }

    public ParameterToken(string name, Type type)
    {
        ParameterName = name;
        Type = type;
    }

    public override string ToString()
    {
        return "Parameter token:\t\t" + ParameterName;
    }
}

public class UnaryOperatorToken : Token
{
    public FilterOperator Operator { get; set; }

    public UnaryOperatorToken(FilterOperator op)
    {
        Operator = op;
    }

    public override string ToString()
    {
        return "Unary operator token:\t\t" + Operator.ToString();
    }
}

After all nodes of the expression are traversed and their equivalent tokens are created, FilterDescriptors can be created. This will be done by calling a method named Build.

遍历表达式的所有节点并创建了它们的等效标记后,就可以创建FilterDescriptor 。 这将通过调用名为Build的方法来完成。

As stated before in "How ExpressionVisitor Works" section, every part of the expression comprises multiple subexpressions. For example, p.Price < 1000 is a binary expression that is made up of the three parts:

如前面“ ExpressionVisitor的工作原理”部分所述,表达式的每个部分都包含多个子表达式。 例如, p.Price < 1000是由三个部分组成的二进制表达式:

  1. p.Price (member expression)

    p.Price (成员表达式)

  2. < ("less than" binary operator)

    < (“小于”二进制运算符)

  3. 1000 (constant expression)

    1000 (常量表达式)

When visited, this 3-part binary expression will produce three different tokens:

当访问时,此三部分二进制表达式将产生三个不同的标记:

  1. A MemberToken for p.Price by VisitMember method

    MemberToken用于p.Price通过VisitMember方法

  2. A BinaryOperatorToken for < by VisitBinary method

    BinaryOperatorToken用于<VisitBinary方法

  3. A ConstantToken for 1000 by VisitConstant method

    ConstantToken1000通过VisitConstant方法

When the Builder method is called, it first creates a Stack<FilterDescriptor> object. Then iterates over the tokens list and based on the type of the current token in the loop, pushes and pops descriptors to and from the stack. This way different tokens, like the three ones in the above example, are combined together to build a single FilterDescriptor.

调用Builder方法时,它首先创建一个Stack<FilterDescriptor>对象。 然后遍历令牌列表,并根据循环中当前令牌的类型,将描述符推入和弹出堆栈。 这样,将不同的令牌(如上例中的三个令牌)组合在一起以构建单个FilterDescriptor

public IEnumerable<FilterDescriptor> Build()
{
    var filters = new Stack<FilterDescriptor>();

    for (var i = 0; i < _tokens.Count; i++)
    {
        var token = _tokens[i];

        switch (token)
        {
            case ParameterToken p:
                var f = getFilter();
                f.FieldName = p.ParameterName;
                filters.Push(f);
                break;

            case BinaryOperatorToken b:
                var f1 = getFilter();

                switch (b.Operator)
                {
                    case FilterOperator.And:
                    case FilterOperator.Or:
                        var ff = filters.Pop();
                        ff.CompositionOperator = b.Operator;
                        filters.Push(ff);
                        break;

                    case FilterOperator.Equal:
                    case FilterOperator.NotEqual:
                    case FilterOperator.LessThan:
                    case FilterOperator.LessThanOrEqual:
                    case FilterOperator.GreaterThan:
                    case FilterOperator.GreaterThanOrEqual:
                        f1.Operator = b.Operator;
                        filters.Push(f1);
                        break;
                }

                break;

            case ConstantToken c:
                var f2 = getFilter();
                f2.Value = c.Value;
                filters.Push(f2);
                break;

            case MemberToken m:
                var f3 = getFilter();
                f3.FieldName = m.MemberName;
                filters.Push(f3);
                break;

            case UnaryOperatorToken u:
                var f4 = getFilter();
                f4.Operator = u.Operator;
                f4.Value = true;
                filters.Push(f4);
                break;

            case MethodCallToken mc:
                var f5 = getFilter();
                f5.Operator = _methodCallMap[mc.MethodName];
                filters.Push(f5);
                break;
        }
    }

    var output = new Stack<FilterDescriptor>();

    while (filters.Any())
    {
        output.Push(filters.Pop());
    }

    return output;

    FilterDescriptor getFilter()
    {
        if (filters.Any())
        {
            var f = filters.First();

            var incomplete = f.Operator == default ||
                                f.CompositionOperator == default ||
                                f.FieldName == default ||
                                f.Value == default;

            if (incomplete)
                return filters.Pop();

            return new FilterDescriptor();
        }

        return new FilterDescriptor();
    }
}

When the Build method returns, all descriptors are ready to be converted to whatever form that is needed.

Build方法返回时,所有描述符都准备好转换为所需的任何形式。

必要的表达式修改 (Necessary Expression Modifications)

Three modifications to the original expression are introduced here, which help a lot in simplifying things. These three changes are my own solution to make the code simpler and more practical. They are not theoretically necessary and one can further develop this example to solve the problem another way and keep the original expression intact.

这里介绍了对原始表达式的三个修改,它们在简化方面有很大帮助。 这三个更改是我自己的解决方案,使代码更简单,更实用。 从理论上讲,它们不是必需的,可以进一步开发此示例,以另一种方式解决问题并保持原始表达完整无缺。

修改布尔MemberAccess表达式 (Modifying Boolean MemberAccess Expressions)

Every condition is defined with three things: a parameter, its value and an operator, which relates the parameter to that value. Now consider this expression: p.OutOfStock where OutOfStock is a Boolean property of the object p. It lacks two of the three parts at the first glance: an operator and a Boolean value; but matter of fact is that it is a short form of this expression: p.OutOfStock == true. On the other hand, the algorithm in this article expects all three parts in order to function as expected. As I have experienced, without the operator and a Boolean value explicitly stated, trying to use this kind of expression as it is, tends to add unnecessary complexity to the solution. For this reason, we visit the expression in two passes. For the first pass, a separate class named BooleanVisitor, which also inherits from ExpressionVisitor, is used. It only overrides VisitMember method. This class is privately nested in the FilterBuilder.

每个条件都由三件事定义:一个参数,其值和一个运算符,该运算符将参数与该值相关联。 现在考虑以下表达式: p.OutOfStock其中OutOfStock是对象p的布尔属性。 乍一看,它缺少三个部分中的两个:运算符和布尔值; 但事实上,这是该表达式的p.OutOfStock == truep.OutOfStock == true 。 另一方面,本文中的算法要求所有这三个部分都能正常运行。 根据我的经验,在没有明确说明运算符和布尔值的情况下,尝试按原样使用这种表达式会给解决方案增加不必要的复杂性。 因此,我们分两次访问了该表达式。 对于第一遍,使用了一个单独的名为BooleanVisitor类,该类也继承自ExpressionVisitor 。 它仅覆盖VisitMember方法。 此类私有嵌套在FilterBuilder

private class BooleanVisitor : ExpressionVisitor
{
    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Type == typeof(bool))
        {
            return Expression.MakeBinary
                   (ExpressionType.Equal, node, Expression.Constant(true));
        }

        return base.VisitMember(node);
    }
}

This overridden method adds two missing parts of a Boolean member access expression to it and returns the modified copy. The second pass needs to be performed afterwards. This is done in the constructor of the FilterBuilder.

此重写的方法向其添加布尔成员访问表达式的两个缺失部分,并返回修改后的副本。 此后需要执行第二遍。 这是在FilterBuilder的构造函数中完成的。

// ctor of the FilterBuilder

public FilterBuilder(Expression expression)
{
    var fixer = new BooleanVisitor();
    var fixedExpression = fixer.Visit(expression);
    base.Visit(fixedExpression);
}

修改否定比较运算符 (Modifying Negated Comparison Operators)

Sometimes relation of a variable to a value in a condition contains a comparison operator combined with a negation operator. An example is !(p.Price > 30000). In such cases, replacing this combination with a single equivalent operator makes things simpler. For example, instead of a ! (not) and > (greater than) operators combined, a <= (less than or equal) operator can be used. The same is valid for string comparison operators too. Any combination of the negation operator and string comparison operators will be replaced by a single equivalent operator that is defined in the FilterOperator enumeration.

有时,条件中变量与值的关系包含比较运算符和否定运算符。 一个例子是!(p.Price > 30000) 。 在这种情况下,用单个等效运算符替换此组合将使事情变得更简单。 例如,代替! (非)和> (大于)运算符结合使用,可以使用<= (小于或等于)运算符。 字符串比较运算符也是如此。 否定运算符和字符串比较运算符的任何组合都将替换为FilterOperator枚举中定义的单个等效运算符。

修改DateTime值 (Modifying DateTime Values)

Two important things should be noted here. First, DateTime values need special attention while visiting an expression tree because a DateTime value can appear in many forms in an expression. Some of those forms that are covered in this solution are:

这里应该注意两个重要的事情。 首先, DateTime值需要特别注意,而来访的表达式树,因为DateTime值可在表达式多种形式出现。 该解决方案涵盖的一些表格包括:

  1. A simple MemberAccess expression: DateTime.Now or DateTime.Date

    一个简单的MemberAccess表达式: DateTime.NowDateTime.Date

  2. Nested MemberAccess expressions: DateTime.Now.Date

    嵌套的MemberAccess表达式: DateTime.Now.Date

  3. A NewExpression: new DateTime(1989, 3, 25)

    A NewExpressionnew DateTime(1989, 3, 25) NewExpression new DateTime(1989, 3, 25)

  4. A NewExpression followed by a MemberAccess expression: new DateTime(1989, 3, 25).Date

    NewExpression后跟一个MemberAccess表达式: new DateTime(1989, 3, 25).Date

When a DateTime value appears as a MemberAccess expression, it should be handled in the VisitMember method. When it appears as a NewExpression, it should be handled in the VisitNew method.

DateTime值显示为MemberAccess表达式时,应在VisitMember方法中对其进行处理。 当它显示为NewExpression ,应在VisitNew方法中进行处理。

Second, one can transfer a DateTime value over the wire in many forms. For instance, it can be converted to a string and formatted arbitrarily; or it can be converted to a long integer (Ticks) and sent as a number. Choosing a specific data type and format is a matter of business requirement or technical constraint. Anyway, here, the Ticks property of the DateTime structure is chosen because of simplicity and also because it can be platform independent.

其次,可以通过多种形式通过网络传输DateTime值。 例如,可以将其转换为string并任意格式化; 或者可以将其转换为长整数( Ticks )并作为数字发送。 选择特定的数据类型和格式是业务需求或技术约束的问题。 无论如何,这里选择DateTime结构Ticks属性是因为简单,也因为它可以独立于平台。

For these two reasons, our expression visitor replaces instances of DateTime structure, with their Ticks equivalent. This means that we have to obtain the value of the Ticks property of DateTime values when the expression visitor code is run. Thus, the expression containing the DateTime value should be compiled to a method and run as in the code below:

由于这两个原因,我们的表达式访问者用其Ticks替换了DateTime结构的实例。 这意味着在运行表达式访问者代码时,我们必须获取DateTime值的Ticks属性的值。 因此,包含DateTime值的表达式应编译为方法,并按以下代码运行:

protected override Expression VisitMember(MemberExpression node)
{
    if (node.Type == typeof(DateTime))
    {
        if (node.Expression == null) // Simple MemberAccess like DateTime.Now
        {
            var lambda = Expression.Lambda<Func<DateTime>>(node);
            var dateTime = lambda.Compile()();
            base.Visit(Expression.Constant(dateTime.Ticks));
            return node;
        }
        else
        {
            switch (node.Expression.NodeType)
            {
                case ExpressionType.New:
                    var lambda = Expression.Lambda<Func<DateTime>>(node.Expression);
                    var dateTime = lambda.Compile()();
                    base.Visit(Expression.Constant(dateTime.Ticks));
                    return node;

                case ExpressionType.MemberAccess: // Nested MemberAccess            
                    if (node.Member.Name != ((MemberExpression)node.Expression).Member.Name) 
                    {
                        var lambda2 = Expression.Lambda<Func<DateTime>>(node);
                        var dateTime2 = lambda2.Compile()();
                        base.Visit(Expression.Constant(dateTime2.Ticks));
                        return node;
                    }
                    break;
            }
        }
    }

    _tokens.Add(new MemberToken(node.Expression + "." + node.Member.Name, node.Type));
    return node;
}

protected override Expression VisitNew(NewExpression node)
{
    if (node.Type == typeof(DateTime))
    {
        var lambda = Expression.Lambda<Func<DateTime>>(node);
        var dateTime = lambda.Compile()();
        base.Visit(Expression.Constant(dateTime.Ticks));
        return node;
    }

    return base.VisitNew(node);
}

转换FilterDescriptors (Converting FilterDescriptors)

As mentioned earlier, when the Build method returns, a collection of FilterDescriptors is ready to be fed to any class or method to be converted to any desired form. In case of a query string, this method can simply be an extension method or a separate class depending on the programmer's preference. Note that every server program will be expecting a predefined set of key-value pairs. For example, suppose there is a server that will look for different parameters of filters in separate array-like key-value pairs. The following extension method will do the job.

如前所述,当Build方法返回时,可以将FilterDescriptor的集合准备馈送到任何类或方法,以将其转换为任何所需的形式。 对于查询字符串,根据程序员的喜好,此方法可以只是扩展方法或单独的类。 请注意,每个服务器程序都将期望一组预定义的键值对。 例如,假设有一个服务器将在类似数组的键/值对中查找过滤器的不同参数。 以下扩展方法将完成此工作。

public static class FilterBuilderExtensions
{
    public static string GetQueryString(this IList<FilterDescriptor> filters)
    {
        var sb = new StringBuilder();

        for (var i = 0; i < filters.Count; i++)
        {
            sb.Append(
                $"filterField[{i}]={filters[i].FieldName}&" +
                $"filterOp[{i}]={filters[i].Operator}&" +
                $"filterVal[{i}]={filters[i].Value}&" +
                $"filterComp[{i}]={filters[i].CompositionOperator}");

            if (i < filters.Count - 1)
                sb.Append("&");
        }

        return sb.ToString();
    }
}

用法示例 (Example Usage)

This simple console program demonstrates how the FilterBuilder can be used.

这个简单的控制台程序演示了如何使用FilterBuilder

ToString method of the FilterDescriptor and all token classes is overridden so their properties can be inspected in the console.

FilterDescriptor和所有令牌类的ToString方法将被覆盖,以便可以在控制台中检查其属性。

class Program
{
    static void Main(string[] args)
    {
        Expression<Func<Product, bool>> exp = p =>
            p.Id == 1009 &&
            !p.OutOfStock &&
            !(p.Price > 30000) &&
            !p.Name.Contains("BMW") &&
            p.ProductionDate > new DateTime(1999, 6, 20).Date;

        var visitor = new FilterBuilder(exp);
        var filters = visitor.Build().ToList();

        Console.WriteLine("Tokens");
        Console.WriteLine("------\n");

        foreach (var t in visitor.Tokens)
        {
            Console.WriteLine(t);
        }

        Console.WriteLine("\nFilter Descriptors");
        Console.WriteLine("------------------\n");

        foreach (var f in filters)
        {
            Console.WriteLine(f);
        }

        Console.WriteLine($"\nQuery string");
        Console.WriteLine("------------\n");
        Console.WriteLine(filters.GetQueryString());

        Console.ReadLine();
    }
}

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public DateTime ProductionDate { get; set; }
    public bool OutOfStock { get; set; } = false;
}

The output:

输出:

Image 3

省略的功能 (Omitted Features)

There surely are many potential improvements to make this solution more robust, but they are intentionally omitted through this article for the sake of brevity. One necessary feature would be supporting parentheses in the expression through a new class that would wrap a collection of FilterDescriptors. Such features require more time and effort that might be covered at a later time. However, I hope the readers are able to grasp the core concepts presented here and develop a better solution on top of this work.

当然,有许多潜在的改进可以使此解决方案更强大,但为简洁起见,本文特意将其省略。 一个必要的功能是通过一个新类支持表达式中的括号,该新类将包装FilterDescriptor的集合。 这样的功能需要更多的时间和精力,以后可能会涉及到。 但是,我希望读者能够掌握此处介绍的核心概念,并在此工作的基础上开发更好的解决方案。

The full source code of the solution is available in the ZIP file attached to this article.

本文所附的ZIP文件中提供了该解决方案的完整源代码。

翻译自: https://www.codeproject.com/Articles/5260863/Translating-Csharp-Lambda-Expressions-to-General-P

c# lambda 表达式

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值