PVS-Studio.com logo
>
>
>
V3219. The variable was changed after i…


V3219. The variable was changed after it was captured in a LINQ method with deferred execution. The original value will not be used when the method is executed.

Анализатор обнаружил изменение захваченной переменной, которая использовалась в LINQ методе с отложенным выполнением. При таком сценарии изначальное значение переменной не будет учитываться.

Рассмотрим пример:

private static List<string> _names = new() { "Tucker", "Bob", "David" };

public static void ProcessNames()
{
  string startLetter = "T";
  var names = _names.Where(c => !c.StartsWith(startLetter));

  startLetter = "B";
  names = names.Where(c => !c.StartsWith(startLetter))
               .ToList();
}

В методе ProcessNames производится фильтрация имён по первой букве. Ожидается, что в результате переменная names будет содержать имена, не начинающиеся на T и B. Однако поведение программы будет иным. При выполнении этого метода в names будет коллекция, состоящая из Tucker и David.

Подобное поведение обусловлено тем, что при вызове Where фильтрация выполняется не в момент вызова, а является отложенной. Так как между двумя вызовами Where переменной startLetter присваивается B, значение T не учитывается при итоговом вычислении.

Это происходит из-за того, что startLetter захвачена при первом вызове Where, и её значение изменилось перед вторым вызовом Where. Как следствие, будет получена коллекция, которая фильтруется только по B.

Подробнее об отложенном выполнении можно узнать здесь.

Чтобы метод работал корректно, нужно либо немедленно выполнить запрос (первый Where) и работать с результатом выполнения, либо использовать другую переменную в лямбда-выражении второго Where.

Рассмотрим первый сценарий:

private static List<string> _names = new() { "Tucker", "Bob", "David" };

public static void ProcessNames()
{
  string startLetter = "T";
  var names = _names.Where(c => !c.StartsWith(startLetter))
                    .ToList();

  startLetter = "B";
  names = names.Where(c => !c.StartsWith(startLetter))
               .ToList();
}

После первого метода Where вызывается ToList, при выполнении которого создаётся коллекция, отфильтрованная с помощью первого Where. На момент изменения захваченной переменной коллекция уже сформирована, следовательно, фильтрация будет выполняться корректно.

Рассмотрим второй вариант исправления:

private static List<string> _names = new() { "Tucker", "Bob", "David" };

public static void ProcessNames()
{
  string startLetter1 = "T";
  var names = _names.Where(c => !c.StartsWith(startLetter1));

  string startLetter2 = "B";
  names = names.Where(c => !c.StartsWith(startLetter2))
               .ToList();
}

В данном случае захваченной переменной не присваивается новое значение между вызовами Where, вместо этого используется переменная startLetter2. В результате фильтрация отработает корректно.