gpt4 book ai didi

c# - ExpressionTree Compile() 方法生成 stackoverflow 异常

转载 作者:太空狗 更新时间:2023-10-30 01:06:50 25 4
gpt4 key购买 nike

我有一个 gridview,我们可以在其中按不同的标准进行过滤。每个条件都是一个表达式。我有一个场景,在调用 Compile 方法时,我可以有超过一千个条件导致我的表达式抛出 StackOverflow。

顺便说一句,我在使用表达式树方面还是个初学者。

这是我为重现 stackoverflow 所做的示例。

var param = Expression.Parameter(typeof(SomeEntity), "SomeEntity");

Expression finalExpression = Expression.Default(typeof(bool));

for (int i = 0; i < 20000; i++) // Create 20000 expressions
{
var left = Expression.Property(param, "OrderID");
var right = Expression.Constant(42.ToString());

var expression = BinaryExpression.Equal(left, right);

finalExpression = Expression.OrElse(finalExpression, expression);
}

var hello = Expression.Lambda(finalExpression, param);
hello.Compile();

我的问题是:有没有办法“减少”这个表达式或任何其他防止计算溢出的解决方案?

谢谢

注意:下面是调试器中的表达式:

(SomeEntity.OrderID == "42")) 
OrElse (SomeEntity.OrderID == "42"))
OrElse (SomeEntity.OrderID == "42"))
OrElse (SomeEntity.OrderID == "42"))
OrElse (SomeEntity.OrderID == "42"))
x20000

最佳答案

我刚刚成功地测试了此代码多达 1,000,000 个条件而没有堆栈溢出 - 我怀疑它可以处理您想要的任意数量的条件。

当在 lambda 表达式上调用 Compile 时,表达式树将递归地向下遍历以编译它;极深的树(像这样)需要很多很多的堆栈帧来完成 - 因此 StackOverflowException

我在下面所做的是在编译表达式并将其推送到已生成的条件集合之前,最多只接受固定数量的条件(由 MaxPredicateConditionCount 设置) .如果预生成表达式的集合达到最大值,则将它们组合成一个新表达式,依此类推。这样我们就可以限制编译表达式所需的递归深度(通过分段进行)。

public class PredicateBuilder<TParameter>
{
private const int MaxPredicateConditionCount = 500;
private readonly List<Expression<Func<TParameter, bool>>> _existingPredicates = new List<Expression<Func<TParameter, bool>>>(MaxPredicateConditionCount);
private readonly ParameterExpression _parameter = Expression.Parameter(typeof(TParameter));
private Expression<Func<TParameter, bool>> _expression;
private Expression _workingPredicate;
private int _workingPredicateConditionCount;
public bool Built { get; private set; }

public Expression<Func<TParameter, bool>> LambdaExpression
{
get
{
if (!Built)
{
return null;
}

return _expression;
}
}

public void AddCondition<TValue>(string propertyName, TValue value)
{
if (Built)
{
throw new InvalidOperationException("Predicate has already been built");
}

var property = Expression.Property(_parameter, propertyName);
var constant = Expression.Constant(value, typeof(TValue));
var equality = Expression.Equal(property, constant);

if (_workingPredicate == null)
{
_workingPredicate = equality;
}
else
{
if (MaxPredicateConditionCount < ++_workingPredicateConditionCount)
{
var compiledWorking = Expression.Lambda<Func<TParameter, bool>>(_workingPredicate, _parameter).Compile();
_existingPredicates.Add(p => compiledWorking(p));

if (_existingPredicates.Count + 1 > MaxPredicateConditionCount)
{
var compiled = BuildExistingPredicates().Compile();
_existingPredicates.Clear();
_existingPredicates.Add(p => compiled(p));
}

_workingPredicate = equality;
_workingPredicateConditionCount = 0;
}
else
{
_workingPredicate = Expression.OrElse(_workingPredicate, equality);
}
}
}

private Expression<Func<TParameter, bool>> BuildExistingPredicates()
{
Expression compileTemp = Expression.Invoke(_existingPredicates[0], _parameter);

for (var i = 1; i < _existingPredicates.Count; ++i)
{
var nextCall = Expression.Invoke(_existingPredicates[i], _parameter);
compileTemp = Expression.OrElse(compileTemp, nextCall);
}

return Expression.Lambda<Func<TParameter, bool>>(compileTemp, _parameter);
}

public void Build()
{
Built = true;
//There were no conditions, assume true
if (_workingPredicate == null)
{
_expression = x => true;
return;
}

_existingPredicates.Add(Expression.Lambda<Func<TParameter, bool>>(_workingPredicate, _parameter));

_expression = BuildExistingPredicates();

_existingPredicates.Clear();
_workingPredicate = null;
_workingPredicateConditionCount = 0;
}

public Func<TParameter, bool> Compile()
{
if (!Built)
{
Build();
}

return _expression.Compile();
}
}

示例实体

public class SomeEntity
{
public string OrderID { get; set; }
}

用法

class Program
{
static void Main()
{
var builder = new PredicateBuilder<SomeEntity>();

for (int i = 0; i < 1000000; i++) // Create 1,000,000 expressions
{
builder.AddCondition("OrderID", "42");
Console.Title = i.ToString();
}

builder.Compile();
}
}

关于c# - ExpressionTree Compile() 方法生成 stackoverflow 异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14144510/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com