C #: элемент с тем же ключом уже был добавлен при компиляции выражения - PullRequest
17 голосов
/ 21 января 2010

Хорошо, вот хитрый. Надеюсь, здесь есть гуру выражений, который может определить, что я делаю здесь неправильно, потому что я просто не понимаю.

Я создаю выражения, которые я использую для фильтрации запросов. Чтобы облегчить этот процесс, у меня есть пара Expression<Func<T, bool>> методов расширения, которые делают мой код чище, и до сих пор они работали хорошо. Я написал тесты для всех из них, кроме одного, который я написал один на сегодня. И этот тест полностью проваливается с ArgumentException с трассировкой стека long . И я просто не понимаю. Тем более, что я некоторое время успешно использовал этот метод в своих запросах!

В любом случае, вот трассировка стека, которую я получаю при выполнении теста:

failed: System.ArgumentException : An item with the same key has already been added.
    at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
    at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
    at System.Linq.Expressions.ExpressionCompiler.PrepareInitLocal(ILGenerator gen, ParameterExpression p)
    at System.Linq.Expressions.ExpressionCompiler.GenerateInvoke(ILGenerator gen, InvocationExpression invoke, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateUnliftedAndAlso(ILGenerator gen, BinaryExpression b)
    at System.Linq.Expressions.ExpressionCompiler.GenerateAndAlso(ILGenerator gen, BinaryExpression b, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateUnliftedOrElse(ILGenerator gen, BinaryExpression b)
    at System.Linq.Expressions.ExpressionCompiler.GenerateOrElse(ILGenerator gen, BinaryExpression b, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateInvoke(ILGenerator gen, InvocationExpression invoke, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateUnliftedAndAlso(ILGenerator gen, BinaryExpression b)
    at System.Linq.Expressions.ExpressionCompiler.GenerateAndAlso(ILGenerator gen, BinaryExpression b, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
    at System.Linq.Expressions.ExpressionCompiler.GenerateLambda(LambdaExpression lambda)
    at System.Linq.Expressions.ExpressionCompiler.CompileDynamicLambda(LambdaExpression lambda)
    at System.Linq.Expressions.Expression`1.Compile()
    PredicateTests.cs(257,0): at Namespace.ExpressionExtensionsTests.WhereWithin_CollectionIsFilteredAsExpected()

Сам тест выглядит следующим образом, но он не выполняется в операторе Compile:

[Test]
public void WhereWithin_CollectionIsFilteredAsExpected()
{
    var range = new[] { Range.Create(2, 7), Range.Create(15, 18) };

    var predicate = Predicate
        .Create<int>(x => x % 2 == 0)
        .AndWithin(range, x => x)
        .Compile();

    var actual = Enumerable.Range(0, 20)
        .Where(predicate)
        .ToArray();

    Assert.That(actual, Is.EqualTo(new[] { 2, 4, 6, 16, 18 }));
}

Я просто не понимаю сообщение об ошибке. Я подумал, что это может быть связано с тем фактом, что я всегда использую x в качестве имени параметра, но, похоже, не помогло, когда я попытался поменять их местами. Что делает меня еще более странным, так это то, что я уже некоторое время использую этот точный метод в больших запросах Linq2Sql, и они всегда хорошо работают. Поэтому в своем тесте я попытался не скомпилировать выражение и использовать AsQueryable, чтобы вместо этого использовать его. Но это только сделало исключение для ToArray. Что здесь происходит? Как я могу это исправить?

Вы можете найти нарушающий и раздражающий код в zip-файле под строкой:


Примечание: Я разместил здесь некоторый связанный код, но после некоторых комментариев я решил извлечь код в его собственный проект, который более четко показывает исключение. И что еще более важно, это может быть выполнено, скомпилировано и отлажено.


Обновление: Упростил пример проекта еще больше с некоторыми предложениями @Mark. Как удаление класса диапазона, а вместо этого просто жесткое кодирование одного константы диапазона. Также добавлен еще один пример, где использование точно такого же метода на самом деле работает нормально. Таким образом, использование метода AndWithin приводит к сбою приложения, в то время как метод WhereWithin действительно работает нормально. Я чувствую себя почти невежественным!

Ответы [ 2 ]

3 голосов
/ 21 января 2010

Я немного реорганизовал ваши методы, чтобы сделать компилятор немного счастливее:

public static Expression<Func<TSubject, bool>> AndWithin<TSubject, TField>(
    this Expression<Func<TSubject, bool>> original,
    IEnumerable<Range<TField>> range, Expression<Func<TSubject, TField>> field) where TField : IComparable<TField>
{
  return original.And(range.GetPredicateFor(field));
}


static Expression<Func<TSource, bool>> GetPredicateFor<TSource, TValue>
    (this IEnumerable<Range<TValue>> range, Expression<Func<TSource, TValue>> selector) where TValue : IComparable<TValue>
{
  var param = Expression.Parameter(typeof(TSource), "x");

  if (range == null || !range.Any())
    return Expression.Lambda<Func<TSource, bool>>(Expression.Constant(false), param);

  Expression body = null;
  foreach (var r in range)
  {
    Expression<Func<TValue, TValue, TValue, bool>> BT = (val, min, max) => val.CompareTo(min) >= 0 && val.CompareTo(max) <= 0;
    var newPart = Expression.Invoke(BT, param,
                                      Expression.Constant(r.Start, typeof(TValue)),
                                      Expression.Constant(r.End, typeof(TValue)));

    body = body == null ? newPart : (Expression)Expression.OrElse(body, newPart);
  }

  return Expression.Lambda<Func<TSource, bool>>(body, param);
}

Оба имеют добавленное ограничение IComparable<TValue> (единственное изменение первого метода).

Во втором, я делаю сравнение с помощью реализации Func Expression, обратите внимание, что func создается внутри цикла ... это второе добавление этого (что он думает, то же самое ...) выражение в старом методе, который взрывается.

Отказ от ответственности: Я до сих пор не до конца понимаю, почему ваш предыдущий метод не сработал, но этот альтернативный подход обходит проблему. Дайте мне знать, если это не то, что вам нужно, и мы попробуем что-нибудь другое.

Кроме того, слава ASKING вопрос хорошо, пример проекта является образцовым.

2 голосов
/ 21 января 2010

Это не ответ, но я надеюсь, что это поможет кому-то найти ответ. Я еще больше упростил код, чтобы он представлял собой один файл и по-прежнему не работал таким же образом. Я переименовал переменные, чтобы «х» не использовался дважды. Я удалил класс Range и заменил его жестко закодированными константами 0 и 1.

using System;
using System.Linq;
using System.Linq.Expressions;

class Program
{
    static Expression<Func<int, bool>> And(Expression<Func<int, bool>> first,
                                           Expression<Func<int, bool>> second)
    {
        var x = Expression.Parameter(typeof(int), "x");
        var body = Expression.AndAlso(Expression.Invoke(first, x), Expression.Invoke(second, x));
        return Expression.Lambda<Func<int, bool>>(body, x);
    }

    static Expression<Func<int, bool>> GetPredicateFor(Expression<Func<int, int>> selector)
    {
        var param = Expression.Parameter(typeof(int), "y");
        var member = Expression.Invoke(selector, param);

        Expression body =
            Expression.AndAlso(
                Expression.GreaterThanOrEqual(member, Expression.Constant(0, typeof(int))),
                Expression.LessThanOrEqual(member, Expression.Constant(1, typeof(int))));

        return Expression.Lambda<Func<int, bool>>(body, param);
    }

    static void Main()
    {
        Expression<Func<int, bool>> predicate = a => true;
        predicate = And(predicate, GetPredicateFor(b => b)); // Comment out this line and it will run without error
        var z = predicate.Compile();
    }
}

Выражение выглядит так в отладчике:

x => (Invoke(a => True,x) && Invoke(y => ((Invoke(b => b,y) >= 0) && (Invoke(b => b,y) <= 1)),x))

Обновление : я упростил его до самого простого, пока все еще выбрасывал то же исключение:

using System;
using System.Linq;
using System.Linq.Expressions;

class Program
{
    static void Main()
    {
        Expression<Func<int, bool>> selector = b => true;
        ParameterExpression param = Expression.Parameter(typeof(int), "y");
        InvocationExpression member = Expression.Invoke(selector, param);
        Expression body = Expression.AndAlso(member, member);
        Expression<Func<int, bool>> predicate = Expression.Lambda<Func<int, bool>>(body, param);
        var z = predicate.Compile();
    }
}
...