Это кодирует, действительно вызывают “доступ к измененному закрытию” проблема?

Беря следующий код, 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));
6
задан jason 11 February 2010 в 06:52
поделиться

3 ответа

У вас очень неприятная проблема, которая возникает из-за мутирования внешней переменной в лямбда-выражении. Проблема заключается в следующем: если вы попытаетесь выполнить итерацию 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
6
ответ дан 10 December 2019 в 02:47
поделиться

Ну, сообщение об ошибке верно в том смысле, что значение 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)) {
    ...
}

При необходимости вы, конечно, могли бы написать многократно используемый метод расширения, принимающий лямбду.

3
ответ дан 10 December 2019 в 02:47
поделиться

Я подозреваю, что изменение значения 'voicesSoFar' в TakeWhile вызывает проблему.

0
ответ дан 10 December 2019 в 02:47
поделиться
Другие вопросы по тегам:

Похожие вопросы: