从方法体内获取表达式树
Getting an expression tree from inside a method's body
上下文:
我正在制作一个 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.
}
}
示例:
举个例子 class 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 语句,然后 returns 它。
问题:
现在我看到,例如,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 that I can make a query from, all I get is a tree with one PropertyExpression
什么也没告诉我。
我试着把它改成一个方法,添加一个 [MethodImpl(MethodImplOptions.AggressiveInlining)]
属性,似乎没有什么能让 Expression
看 inside 我的方法 / 属性.
问题:
是否有可能使 Expression
看起来更深入 - 进入 属性 getter / 方法体?如果没有 - 是否有 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>>
。简短回答:不,你不能检查它*
如果您将 class 定义替换为:
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)
为了解决这个问题,您需要编写一些辅助方法,让您 AND
和 OR
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);
return 你会 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,但您将陷入困境。
上下文:
我正在制作一个 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.
}
}
示例:
举个例子 class 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 语句,然后 returns 它。
问题:
现在我看到,例如,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 that I can make a query from, all I get is a tree with one PropertyExpression
什么也没告诉我。
我试着把它改成一个方法,添加一个 [MethodImpl(MethodImplOptions.AggressiveInlining)]
属性,似乎没有什么能让 Expression
看 inside 我的方法 / 属性.
问题:
是否有可能使 Expression
看起来更深入 - 进入 属性 getter / 方法体?如果没有 - 是否有 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>>
。简短回答:不,你不能检查它*
如果您将 class 定义替换为:
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)
为了解决这个问题,您需要编写一些辅助方法,让您 AND
和 OR
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);
return 你会 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,但您将陷入困境。