Я пытаюсь использовать PredicateBuilder LinqKit для создания некоторой вложенной логики для сравнения с SQL, и это отлично работает, когда я указываю условия вручную, но когда я помещаю код в цикл, я получаю ArgumentOutOfRangeException
позже, когда пытаюсь запрос на основе построенного мной предиката. Вот мой код:
var outerPredicate = PredicateBuilder.New<DataRow>();
var innerPredicate = PredicateBuilder.New<DataRow>();
for (int i = 0; i < 4; i++)
{
innerPredicate = PredicateBuilder.New<DataRow>();
for (int j = 0; j < 1; j++)
{
innerPredicate = innerPredicate.Or(y => y.Field<string>(sqlNames[i]) == filters[i][j]);
}
outerPredicate = outerPredicate.And(innerPredicate);
}
Этот код всегда выдает одно и то же ArgumentOutOfRangeException
, даже если я настрою каждый цикл на запуск только один раз, но этот код работает отлично:
var outerPredicate = PredicateBuilder.New<DataRow>();
var innerPredicate = PredicateBuilder.New<DataRow>();
innerPredicate = innerPredicate.Or(y => y.Field<string>(sqlNames[3]) == filters[3][0]);
innerPredicate = outerPredicate.And(innerPredicate);
innerPredicate = PredicateBuilder.New<DataRow>();
innerPredicate = innerPredicate.Or(y => y.Field<string>(sqlNames[2]) == filters[2][0]);
outerPredicate = outerPredicate.And(innerPredicate);
innerPredicate = PredicateBuilder.New<DataRow>();
innerPredicate = innerPredicate.Or(y => y.Field<string>(sqlNames[1]) == filters[1][0]);
outerPredicate = outerPredicate.And(innerPredicate);
innerPredicate = PredicateBuilder.New<DataRow>();
innerPredicate = innerPredicate.Or(y => y.Field<string>(sqlNames[0]) == filters[0][0]);
outerPredicate = outerPredicate.And(innerPredicate);
Насколько я могу судить, эти два фрагмента кода функционально идентичны. Буду очень признателен всем, кто сможет разгадать для меня эту загадку.
Похоже, вы получаете еще одну строку в цикле с i=0, поэтому вы теряете ссылку. Так что не делайте New<> для i==0
@RuneAndersen Как это? Цикл идет от i=0 до i=3, как и ручной метод. Я установил точки останова и прошел цикл на каждой итерации, и он определенно выполняется 4 раза.
это первый внутреннийPredicate = PredicateBuilder.New<DataRow>(); это перезаписывает каждый раз
@RuneAndersen Да, я знаю, как и innerPredicate = PredicateBuilder.New<DataRow>();
перезаписывает его во втором методе, только после того, как outerPredicate = outerPredicate.And(innerPredicate);
запускается в обоих случаях.
Я не эксперт, но примеры, которые я рассмотрел, задают предикат с помощью PredicateBuilder.True<DataRow>()
для цепочек AND и PredicateBuilder.False<DataRow>()
для цепочек OR вместо PredicateBuilder.New<DataRow>()
. Не уверен, что это может быть проблемой. Помимо этого, я просмотрел две версии вашего кода, и они кажутся эквивалентными, за исключением порядка индекса (0..3 против 3..0) и дополнительной (отброшенной) инициализации - обе из которых кажутся недействительными. дела по уходу.
@TN Да, ты прав, я недостаточно внимательно присмотрелся.
Я думаю, проблема в том, что в развертывании есть константы, а в цикле есть переменные, в которых выражения внутри Or вычисляются лениво. Исправлением может быть добавление var ii=i; и вар jj=j; внутри цикла и используйте их вместо этого.
(Мои предыдущие комментарии об инициализаторе True/False, вероятно, были тупиком, ссылаясь на устаревшее использование.) Я думаю, что @RuneAndersen справился с этим. После последних итераций цикла значения i
и j
, скорее всего, останутся со значениями 4 и 1, что вызывает ArgumentOutOfRangeException
, когда предикаты вычисляются лениво. (На самом деле может существовать четыре отдельных экземпляра j
, каждый со значением = 1, из-за привязок замыкания.)
Это должно сделать это:
var outerPredicate = PredicateBuilder.New<DataRow>();
var innerPredicate = PredicateBuilder.New<DataRow>();
for (int i = 0; i < 4; i++)
{
innerPredicate = PredicateBuilder.New<DataRow>();
for (int j = 0; j < 1; j++)
{
var ii=i;var jj=j;
innerPredicate = innerPredicate.Or(y => y.Field<string>(sqlNames[ii]) == filters[ii][jj]);
}
outerPredicate = outerPredicate.And(innerPredicate);
}
см.: Захваченная переменная в цикле в C#
Ух ты, я бы никогда об этом не подумал, какая странная причуда. В ответе по этой ссылке говорится, что это исправлено в C# 5, но я использую .NET 6.0, и эта проблема, похоже, все еще присутствует, или это только для foreach?
Смотрите этот ответ.
@GertArnold Спасибо! Очень обидно, что это небезопасное поведение было исправлено в foreach
, но не в for
, по крайней мере теперь я знаю.
Вы также можете использовать foreach (var i in Enumerable.Range(0, 4))
(и аналогичное для j
). Почему? Потому что foreach
определяет и инициализирует новую локальную переменную для каждой итерации «с областью действия, которая распространяется на встроенный оператор» (спецификация языка C#). Напротив, переменные цикла for
инициализируются один раз и изменяются (обычно увеличиваются) с помощью for_iterator после каждой итерации цикла. Это требует более широкой и постоянной области действия: «Областью локальной переменной, объявленной for_initializer, являются for_initializer, for_condition, for_iterator и Embedded_statement».
@TN Да, я понимаю, что поведение foreach
отличается и сравнительно безопаснее, чем for
, но это все равно кажется очень неинтуитивным и на самом деле просто ошибочной функциональностью, с которой могут происходить подобные вещи for
, особенно когда кажется очень непоследовательным относительно того, когда это происходит. на самом деле проблема или нет. На самом деле все это означает, что на практике я, вероятно, никогда больше не буду использовать циклы for
в C#, а просто буду использовать foreach
для репликации той же функциональности.
«Исправление» оператора for
, чтобы иметь отдельные копии переменных итерации для каждого выполнения цикла, потребовало бы действительно странного переписывания семантики. Типичный for_iterator i++
, возможно, придется переосмыслить как нечто вроде var i_next = i; i_next++
. Операторы, имеющие несколько переменных, определенных в for_initializer, и несколько произвольных выражений в for_iterator, еще больше усложнят ситуацию. Область действия каждого экземпляра переменной также сложна, так как он предшествует for_initializer или for_iterator, for_condition, встроенному оператору и следует за for_iterator.
... Перекрывающаяся область действия одноименных (но разных) переменных (я ожидаю) потребует дальнейших хитростей в IL-представлении и в отладчике. В то же время я считаю, что полученная семантика может быть еще менее интуитивно понятной для понимания программиста. Итак, я думаю, что оставить область видимости переменных как есть, вероятно, было правильным решением со стороны разработчиков языка. Нам просто нужно принять это поведение как часть языка, что может быть одним из тех типов поведения, с которыми вы сталкиваетесь однажды, извлекаете уроки и избегаете их в будущем. (внимание: @BobSmith)
@TN Я просто не уверен, что вижу причину, по которой имело бы смысл использовать for
, когда такое поведение существует. Это просто кажется менее безопасной версией foreach (var i in Enumerable.Range(x, y))
, именно так for
работает на других языках и как вы интуитивно думаете, что он работает в C#.
Я имею в виду, какой смысл использовать цикл for, если вы не можете использовать переменную-итератор внутри цикла, например, в качестве индекса, как я пытался это сделать? В более чем 90% случаев, когда я пишу цикл for, я каким-то образом читаю переменную итератора. Если цикл не невероятно прост, если вы не обращаетесь к переменной итератора, то почти всегда имеет смысл использовать вместо нее foreach
или while
.
@BobSmith - это ошибка только в том случае, если: (1) вы используете for
вместо foreach
, (2) переменная итерации напрямую используется в лямбда-функции (или аналогичной конструкции замыкания) и (3) эта лямбда-функция сохраняется и имеет отложенное выполнение, которое происходит после этой итерации цикла. Если ни одно из последних двух условий не применимо, использовать цикл for
совершенно безопасно.
@RuneAndersen Как бы я разбил это на что-то большее, чем это? Мне не удалось увидеть значение
outerPredicate
в своем отладчике, но похоже, что я собираю его одинаково обоими методами. Просто по какой-то причине ему не нравится, когда я делаю это в цикле.