С#: элемент с тем же ключом уже добавлен при компиляции выражения
Хорошо, здесь сложно. Надеюсь, здесь есть гуру выражения, который может определить, что я делаю неправильно здесь, потому что я просто не понимаю его.
Я создаю выражения, которые я использую для фильтрации запросов. Чтобы облегчить этот процесс, у меня есть несколько методов расширения Expression<Func<T, bool>>
, которые делают мой код более чистым, и до сих пор они хорошо работали. Я написал тесты для всех из них, кроме одного, который я написал один на сегодня. И этот тест полностью завершился с ArgumentException
с длинной трассировкой стека. И я просто не понимаю. Тем более, что я успешно использовал этот метод в моих запросах!
В любом случае, вот трассировка стека, которую я получаю при запуске теста:
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 работает нормально. Я чувствую себя довольно невежественным!
Ответы
Ответ 1
Я немного переработал ваши методы, чтобы сделать компилятор более счастливым:
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
Это не ответ, но я надеюсь, что это поможет кому-то найти ответ. Я упростил код дальше, так что это всего лишь один файл и по-прежнему не работает одинаково. Я переименовал переменные так, чтобы "x" не использовался дважды. Я удалил класс Range и заменил его на hardcoded константы 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();
}
}