4

さまざまな基準でフィルタリングできるグリッドビューがあります。各基準は式です。コンパイルメソッドを呼び出すときに式がStackOverflowをスローする原因となる1000を超える基準を持つことができるシナリオがあります。

ところで、私はまだ式ツリーの使用の初心者です。

これは、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
4

1 に答える 1

5

スタックオーバーフローなしで、このコードを最大1,000,000の条件で正常にテストしました。ただし、必要な数の条件を処理できると思います。

ラムダ式でが呼び出されると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();
    }
}
于 2013-01-03T19:07:06.130 に答える