内容:

我正在做一个Expression解析器,它将接受我的LINQ查询,并将它们转换为特定的字节数组。考虑将ORM用于自定义数据存储,等等。为了示例,我将在示例中使用SQL。

class ExpressionParser<T>
{
    public string ParseWhere(Expression<Func<T, bool>> predicate)
    {
        // Takes an expression, follows the expression tree, building an SQL query.
    }
}


例:

举一个带有一些虚拟属性的示例类FooData

class FooData
{
    public int Status { get; set; }
    public bool Active { get; set; }
}




var parser = new ExpressionParser<FooData>();
var query = parser.ParseWhere(foo => foo.Active && (foo.Status == 3 || foo.Status == 4));
// Builds "WHERE active AND (status = 3 OR status = 4)"


这很好用,我的解析器遍历表达式树,构建WHERE语句,然后返回它。

问题:

现在,我看到例如Active && (Status == 3 || Status == 4)是一个特例,将在整个项目中使用。所以自然地,我将其提取到一个计算属性中:

class FooData
{
    public int Status { get; set; }
    public bool Active { get; set; }
    public bool IsSpecialThing => Active && (Status == 3 || Status == 4);
}




var query = parser.ParseWhere(foo => foo.IsSpecialThing);


如果对表达式求值,结果将是相同的。但是,这不再起作用。除了可以查询的a full expression tree之外,我所得到的只是a tree with one PropertyExpression一无所获。

我尝试将其更改为方法,添加了[MethodImpl(MethodImplOptions.AggressiveInlining)]属性,似乎没有什么使Expression出现在我的方法/属性内。

题:

是否可以使Expression看起来更深-进入属性获取器/方法主体?如果不是-是否可以替代Expression

如果根本不可能,在这种情况下应该怎么办?复制一个项目中数十(数百)次查询的大部分内容确实很糟糕。

最佳答案

这里的问题是:

public bool IsSpecialThing => Active && (Status == 3 || Status == 4);


等效于此:

public bool IsSpecialThing { get { return Active && (Status == 3 || Status == 4); } }


请注意,它们都是编译方法。您可以看到此信息,因为类型是Func<FooData,bool>,而不是Expression<Func<FooData,bool>>。简短答案:不,您无法检查*

如果您用以下内容替换您的类定义:

public class FooData
{
    public int Status { get; set; }
    public bool Active { get; set; }

    public static Expression<Func<FooData, bool>> IsSpecialThing = (foo) => foo.Active && (foo.Status == 3 || foo.Status == 4);
}


然后可以按以下方式使用它:

var parser = new ExpressionParser<FooData>();
var query = parser.ParseWhere(FooData.IsSpecialThing);


注意,这带来了更多的困难。我假设您想编写类似以下内容的内容:

ParseWhere(f => f.IsSpecialThing() && f.SomethingElse)


这里的问题是IsSpecialThing是它自己的lambda函数以及它自己的参数。因此,它相当于写:

ParseWhere(f => (ff => IsSpecialThing(ff)) && f.SomethingElse)


为了解决这个问题,您需要编写一些帮助程序方法,这些方法可以使您正确地ANDOR LambdaExpression

public class ParameterRewriter<TArg, TReturn> : ExpressionVisitor
{
    Dictionary<ParameterExpression, ParameterExpression> _mapping;
    public Expression<Func<TArg, TReturn>> Rewrite(Expression<Func<TArg, TReturn>> expr, Dictionary<ParameterExpression, ParameterExpression> mapping)
    {
        _mapping = mapping;
        return (Expression<Func<TArg, TReturn>>)Visit(expr);
    }

    protected override Expression VisitParameter(ParameterExpression p)
    {
        if (_mapping.ContainsKey(p))
            return _mapping[p];
        return p;
    }
}


上面将采用参数之间的映射,并在给定的表达式树中替换它们。

利用它:

public static class ExpressionExtensions
{
    public static Expression<Func<T, bool>> OrElse<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
    {
        var rewrittenRight = RewriteExpression(left, right);

        return Expression.Lambda<Func<T, bool>>(Expression.OrElse(left.Body, rewrittenRight.Body), left.Parameters);
    }

    public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
    {
        var rewrittenRight = RewriteExpression(left, right);

        return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left.Body, rewrittenRight.Body), left.Parameters);
    }

    private static Expression<Func<T, bool>> RewriteExpression<T>(Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
    {
        var mapping = new Dictionary<ParameterExpression, ParameterExpression>();
        for (var i = 0; i < left.Parameters.Count; i++)
            mapping[right.Parameters[i]] = left.Parameters[i];

        var pr = new ParameterRewriter<T, bool>();
        var rewrittenRight = pr.Rewrite(right, mapping);
        return rewrittenRight;
    }
}


如果您这样编写,以上内容实际上就是:

Expression<Func<FooData, bool>> a = f => f.Active;
Expression<Func<FooData, bool>> b = g => g.Status == 5;
Expression<Func<FooData, bool>> c = a.AndAlso(b);


将返回您f => f.Active && f.Status == 5(请注意如何将参数g替换为f

放在一起:

var parser = new ExpressionParser<FooData>();
var result = parser.ParseWhere(FooData.IsSpecialThing.AndAlso(f => f.Status == 6));



*请注意,从技术上讲可以解析生成的IL,但是您将陷入困境。

关于c# - 从方法主体内部获取表达式树,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/36001528/

10-14 12:47