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
. В результате фильтрация отработает корректно.