Беря следующий код, Resharper говорит мне это voicesSoFar
и voicesNeededMaximum
вызовите "доступ к измененному закрытию". Я читал о них, но что озадачивает меня, вот то, что Resharper предлагает фиксировать это путем извлечения переменных для исправления перед запросом LINQ. Но это - то, где они уже!
Resharper прекращает жаловаться, добавляю ли я просто int voicesSoFar1 = voicesSoFar
прямо после int voicesSoFar = 0
. Есть ли некоторая странная логика, я не понимаю, что это делает предложение Resharper корректным? Или есть ли путь к безопасно "измененным закрытиям доступа" в случаях как они, не вызывая ошибки?
// this takes voters while we have less than 300 voices
int voicesSoFar = 0;
int voicesNeededMaximum = 300;
var eligibleVoters =
voters.TakeWhile((p => (voicesSoFar += p.Voices) < voicesNeededMaximum));
У вас очень неприятная проблема, которая возникает из-за мутирования внешней переменной в лямбда-выражении. Проблема заключается в следующем: если вы попытаетесь выполнить итерацию eligibleVoters
дважды (foreach(var voter in eligibleVoters) { Console.WriteLine(voter.Name); }
и сразу после (foreach(var voter in eligibleVoters) { Console.WriteLine(voter.Name); }
), вы не увидите одинакового результата. Это просто неправильно с точки зрения функционального программирования.
Вот метод расширения, который будет накапливать до тех пор, пока некоторое условие на аккумуляторе не станет истинным:
public static IEnumerable<T> TakeWhileAccumulator<T, TAccumulate>(
this IEnumerable<T> elements,
TAccumulate seed,
Func<TAccumulate, T, TAccumulate> accumulator,
Func<TAccumulate, bool> predicate
) {
TAccumulate accumulate = seed;
foreach(T element in elements) {
if(!predicate(accumulate)) {
yield break;
}
accumulate = accumulator(accumulate, element);
yield return element;
}
}
Использование:
var eligibleVoters = voters.TakeWhileAccumulator(
0,
(votes, p) => votes + p.Voices,
i => i < 300
);
Таким образом, выше сказано накапливать голоса, пока мы набрали менее 300 голосов.
Затем с:
foreach (var item in eligibleVoters) { Console.WriteLine(item.Name); }
Console.WriteLine();
foreach (var item in eligibleVoters) { Console.WriteLine(item.Name); }
Выход:
Alice
Bob
Catherine
Alice
Bob
Catherine
Ну, сообщение об ошибке верно в том смысле, что значение voicesSoFar
не сохраняется во время операции. В чисто "функциональных" терминах (а ламбды действительно предназначены для функциональных действий) это будет сбивать с толку.
Например, интересным тестом будет:
что произойдет, если я выполню итерацию запроса дважды?
Например:
int count = voters.Count();
var first = voters.FirstOrDefault();
Я полагаю, вы видите... 10
, null
- путаница. Следующий пример:
public static IEnumerable<Foo> TakeVoices(
this IEnumerable<Foo> voices, int needed)
{
int count = 0;
foreach (Foo voice in voices)
{
if (count >= needed) yield break;
yield return voice;
count += voice.Voices;
}
}
....
foreach(var voice in sample.TakeVoices(numberNeeded)) {
...
}
При необходимости вы, конечно, могли бы написать многократно используемый метод расширения, принимающий лямбду.
Я подозреваю, что изменение значения 'voicesSoFar' в TakeWhile вызывает проблему.