PredicateBuilder не работает в цикле, а работает вручную

Я пытаюсь использовать 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);

Насколько я могу судить, эти два фрагмента кода функционально идентичны. Буду очень признателен всем, кто сможет разгадать для меня эту загадку.

@RuneAndersen Как бы я разбил это на что-то большее, чем это? Мне не удалось увидеть значение outerPredicate в своем отладчике, но похоже, что я собираю его одинаково обоими методами. Просто по какой-то причине ему не нравится, когда я делаю это в цикле.

Bob Smith 20.04.2024 00:19

Похоже, вы получаете еще одну строку в цикле с i=0, поэтому вы теряете ссылку. Так что не делайте New<> для i==0

Rune Andersen 20.04.2024 00:22

@RuneAndersen Как это? Цикл идет от i=0 до i=3, как и ручной метод. Я установил точки останова и прошел цикл на каждой итерации, и он определенно выполняется 4 раза.

Bob Smith 20.04.2024 00:23

это первый внутреннийPredicate = PredicateBuilder.New<DataRow>(); это перезаписывает каждый раз

Rune Andersen 20.04.2024 00:26

@RuneAndersen Да, я знаю, как и innerPredicate = PredicateBuilder.New<DataRow>(); перезаписывает его во втором методе, только после того, как outerPredicate = outerPredicate.And(innerPredicate); запускается в обоих случаях.

Bob Smith 20.04.2024 00:27

Я не эксперт, но примеры, которые я рассмотрел, задают предикат с помощью PredicateBuilder.True<DataRow>() для цепочек AND и PredicateBuilder.False<DataRow>() для цепочек OR вместо PredicateBuilder.New<DataRow>(). Не уверен, что это может быть проблемой. Помимо этого, я просмотрел две версии вашего кода, и они кажутся эквивалентными, за исключением порядка индекса (0..3 против 3..0) и дополнительной (отброшенной) инициализации - обе из которых кажутся недействительными. дела по уходу.

T N 20.04.2024 00:32

@TN Да, ты прав, я недостаточно внимательно присмотрелся.

Rune Andersen 20.04.2024 00:45

Я думаю, проблема в том, что в развертывании есть константы, а в цикле есть переменные, в которых выражения внутри Or вычисляются лениво. Исправлением может быть добавление var ii=i; и вар jj=j; внутри цикла и используйте их вместо этого.

Rune Andersen 20.04.2024 00:52

(Мои предыдущие комментарии об инициализаторе True/False, вероятно, были тупиком, ссылаясь на устаревшее использование.) Я думаю, что @RuneAndersen справился с этим. После последних итераций цикла значения i и j, скорее всего, останутся со значениями 4 и 1, что вызывает ArgumentOutOfRangeException, когда предикаты вычисляются лениво. (На самом деле может существовать четыре отдельных экземпляра j, каждый со значением = 1, из-за привязок замыкания.)

T N 20.04.2024 01:01
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
9
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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?

Bob Smith 20.04.2024 05:48

Смотрите этот ответ.

Gert Arnold 20.04.2024 09:42

@GertArnold Спасибо! Очень обидно, что это небезопасное поведение было исправлено в foreach, но не в for, по крайней мере теперь я знаю.

Bob Smith 22.04.2024 21:54

Вы также можете использовать foreach (var i in Enumerable.Range(0, 4)) (и аналогичное для j). Почему? Потому что foreach определяет и инициализирует новую локальную переменную для каждой итерации «с областью действия, которая распространяется на встроенный оператор» (спецификация языка C#). Напротив, переменные цикла for инициализируются один раз и изменяются (обычно увеличиваются) с помощью for_iterator после каждой итерации цикла. Это требует более широкой и постоянной области действия: «Областью локальной переменной, объявленной for_initializer, являются for_initializer, for_condition, for_iterator и Embedded_statement».

T N 22.04.2024 23:31

@TN Да, я понимаю, что поведение foreach отличается и сравнительно безопаснее, чем for, но это все равно кажется очень неинтуитивным и на самом деле просто ошибочной функциональностью, с которой могут происходить подобные вещи for, особенно когда кажется очень непоследовательным относительно того, когда это происходит. на самом деле проблема или нет. На самом деле все это означает, что на практике я, вероятно, никогда больше не буду использовать циклы for в C#, а просто буду использовать foreach для репликации той же функциональности.

Bob Smith 29.04.2024 17:02

«Исправление» оператора for, чтобы иметь отдельные копии переменных итерации для каждого выполнения цикла, потребовало бы действительно странного переписывания семантики. Типичный for_iterator i++, возможно, придется переосмыслить как нечто вроде var i_next = i; i_next++. Операторы, имеющие несколько переменных, определенных в for_initializer, и несколько произвольных выражений в for_iterator, еще больше усложнят ситуацию. Область действия каждого экземпляра переменной также сложна, так как он предшествует for_initializer или for_iterator, for_condition, встроенному оператору и следует за for_iterator.

T N 29.04.2024 19:00

... Перекрывающаяся область действия одноименных (но разных) переменных (я ожидаю) потребует дальнейших хитростей в IL-представлении и в отладчике. В то же время я считаю, что полученная семантика может быть еще менее интуитивно понятной для понимания программиста. Итак, я думаю, что оставить область видимости переменных как есть, вероятно, было правильным решением со стороны разработчиков языка. Нам просто нужно принять это поведение как часть языка, что может быть одним из тех типов поведения, с которыми вы сталкиваетесь однажды, извлекаете уроки и избегаете их в будущем. (внимание: @BobSmith)

T N 29.04.2024 19:12

@TN Я просто не уверен, что вижу причину, по которой имело бы смысл использовать for, когда такое поведение существует. Это просто кажется менее безопасной версией foreach (var i in Enumerable.Range(x, y)), именно так for работает на других языках и как вы интуитивно думаете, что он работает в C#.

Bob Smith 29.04.2024 19:30

Я имею в виду, какой смысл использовать цикл for, если вы не можете использовать переменную-итератор внутри цикла, например, в качестве индекса, как я пытался это сделать? В более чем 90% случаев, когда я пишу цикл for, я каким-то образом читаю переменную итератора. Если цикл не невероятно прост, если вы не обращаетесь к переменной итератора, то почти всегда имеет смысл использовать вместо нее foreach или while.

Bob Smith 29.04.2024 19:37

@BobSmith - это ошибка только в том случае, если: (1) вы используете for вместо foreach, (2) переменная итерации напрямую используется в лямбда-функции (или аналогичной конструкции замыкания) и (3) эта лямбда-функция сохраняется и имеет отложенное выполнение, которое происходит после этой итерации цикла. Если ни одно из последних двух условий не применимо, использовать цикл for совершенно безопасно.

T N 29.04.2024 19:52

Другие вопросы по теме