这段代码真的会导致“访问修改后的闭包”吗?问题?

发布于 2024-08-21 05:58:33 字数 605 浏览 1 评论 0原文

使用以下代码,Resharper 告诉我 voicesSoFarvoicesNeededMaximum 导致“访问修改后的闭包”。我读到了这些内容,但令我困惑的是,Resharper 建议通过将变量提取到 LINQ 查询之前来解决此问题。但这就是他们已经在的地方了!

如果我只是在 int voicesSoFar = 0 之后添加 int voicesSoFar1 = voicesSoFar,Resharper 就会停止抱怨。是否有一些我不明白的奇怪逻辑使 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));

Taking the following code, Resharper tells me that voicesSoFar and voicesNeededMaximum cause "access to a modified closure". I read about these but what puzzles me here is that Resharper suggests fixing this by extracting the variables to right before the LINQ query. But that is where they are already!

Resharper stops complaining if I merely add int voicesSoFar1 = voicesSoFar right after int voicesSoFar = 0. Is there some weird logic I do not understand that makes Resharper's suggestion correct? Or is there a way to safely "access modified closures" in cases like these without causing bugs?

// 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));

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

画中仙 2024-08-28 05:58:33

将外部变量转变为 lambda 表达式会产生一个非常棘手的问题。问题是这样的:如果您尝试迭代 eligibleVoters 两次 (foreach(var voter in EligibleVoters) { Console.WriteLine(voter.Name); } 并在 (< 这是不正确的。

code>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

You have a very nasty problem that arises from mutating an outer variable to the lambda expression. The problem is this: if you try to iterate eligibleVoters twice (foreach(var voter in eligibleVoters) { Console.WriteLine(voter.Name); } and immediately after (foreach(var voter in eligibleVoters) { Console.WriteLine(voter.Name); }) you will not see the same output. That is just not right from a functional programming perspective.

Here is an extension method that will accumulate until some condition on the accumulator is true:

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;
    }
}

Usage:

var eligibleVoters = voters.TakeWhileAccumulator(
                         0,
                         (votes, p) => votes + p.Voices, 
                         i => i < 300
                     );

Thus the above says accumulate voices while we have accumulated less than 300 votes.

Then with:

foreach (var item in eligibleVoters) { Console.WriteLine(item.Name); }
Console.WriteLine();
foreach (var item in eligibleVoters) { Console.WriteLine(item.Name); }

Output is:

Alice
Bob
Catherine

Alice
Bob
Catherine
一世旳自豪 2024-08-28 05:58:33

好吧,错误消息是正确的,因为在操作过程中未保留 voicesSoFar 的值。在纯粹的“函数式”术语中(并且 lambda 确实被设计为函数式操作),这会令人困惑。

例如,一个有趣的测试是:

如果我迭代查询两次会发生什么?

例如:

int count = voters.Count();
var first = voters.FirstOrDefault();

我相信您可以看到... 10null - 令人困惑。以下内容是可重复的:

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)) {
    ...
}

如果您需要,您当然可以编写一个采用 lambda 的可重用扩展方法。

Well, the error message is correct in as much that the value of voicesSoFar is not preserved during the operation. In pure "functional" terms (and lambdas are really designed to act functionally) it will be confusing.

For example, an interesting test would be:

what happens if I iterate the query twice?

For example:

int count = voters.Count();
var first = voters.FirstOrDefault();

I believe you can see... 10, null - confusing. The following would be repeatable:

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)) {
    ...
}

If you needed you could of course write a reusable extension method that took a lambda.

魔法少女 2024-08-28 05:58:33

我怀疑修改 TakeWhile 中的“voicesSoFar”值导致了该问题。

I suspect that modifying the 'voicesSoFar' value in TakeWhile is causing the problem.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文