【问题标题】:Modifying element inside a loop messes with the for loop修改循环内的元素会与 for 循环混淆
【发布时间】:2020-08-01 09:23:18
【问题描述】:

我正在使用以下 for 循环来遍历 IEnumerable:

for (int i = 0; i < items.Count(); i++)
{
    if (cancellationToken.IsCancellationRequested)
    {
        return;
    }

    var obj = items.ElementAt(i);

    obj.TranslatedText = await Task.Run(() => Translator.Translate(obj.EnglishText, "English", File.Lang));

    progress.Report(i + 1);

    await Task.Delay(DELAY);
}

上面的代码跳过了备用元素。即使计数为 7,循环也只运行了 4 次。

我尝试将for 循环替换为等效的foreach 循环:

int current = 0;
foreach (var item in items)
{
    if (cancellationToken.IsCancellationRequested)
    {
        return;
    }

    item.TranslatedText = await Task.Run(() => Translator.Translate(item.EnglishText, "English", File.Lang));

    progress.Report(++current);

    await Task.Delay(DELAY);
}

它工作正常,我不知道这两者之间有什么不同。

我又挖了一点,发现如果我删除线

obj.TranslatedText = await Task.Run(() =&gt; Translator.Translate(obj.EnglishText, "English", File.Lang));

从第一个示例中,它执行得很好。

我不能修改 IEnumerable 的内容吗?只是好奇。

更新 1 我在下面发布了一个可重现的示例。

https://pastebin.com/5ZXky7iX

【问题讨论】:

  • 这里的items 是什么?你能提供一个minimal reproducible example吗? (例如,如果items 是查询的结果,您可能只需要先实现结果......由于您使用ElementAt 的方式,这也可以避免多次迭代它。)
  • 这种解释不如minimal reproducible example 真正有用...如果更改item.TranslatedText 会影响ObservableCollection,那肯定会解释奇怪。
  • @JonathanAlfaro:OP 没有在 lambda 表达式中捕获 i,所以这不会有任何区别。老实说,虽然我们看不到minimal reproducible example,但可能不值得推测发生了什么。
  • 正如我所想 - items 不是 ObservableCollection,它是查询的结果。将添加一个答案。
  • 请注意,这里的异步性无关紧要 - 您可以简化代码以将其删除,以及 BindableBase。这是一个更简单的复制:gist.github.com/jskeet/f23466beebf977b56fd7a1907771f950

标签: c# ienumerable


【解决方案1】:

现在我们有了一个完整的例子,我们可以看到问题所在。您用于items 的集合是一个依赖于TranslatedText 的查询:

var source = collection.Where(x => string.IsNullOrEmpty(x.TranslatedText));

您对obj 执行的操作会使该查询的结果无效,对于您正在处理的项目:

obj.TranslatedText = "something";

所以在你的for循环中,最初所有10个Translation对象都满足条件,所以Count()是10。在循环的第一次迭代中,你访问第一个元素(元素0),并设置@ 987654330@转"something"

现在在循环的每次迭代中,您都在计算“查询的当前结果” - 现在是 9。然后您可以通过索引访问元素查询的当前结果 - 所以当i 为 1 时,您将跳过查询的第一个匹配项并访问第二个匹配项。但这不是原始集合中的第二个匹配项 - 它是当前查询的第二个匹配项,它已经跳过了第一个元素,因为您修改了它以设置翻译。因此,在循环的第二次迭代中跳过了原始元素索引 1,而是将翻译文本设置为原始元素索引 2。然后Count() 变成 8 等等。

使用foreach 循环,您只需对查询进行一次迭代 - 虽然您仍在使“您正在查看的当前元素”的查询条件无效,但查询处理不需要无论如何,请再次检查。

所以要么使用foreach 循环,或者如果你想通过索引访问元素,你应该首先实现查询。例如,您可以使用:

// Evaluate the query once, storing the results in a list
var list = items.ToList();
// Now you can operate on the list without worrying about the query
// being reevaluated.
for (int i = 0; i < list.Count; i++)
{
    if (cancellationToken.IsCancellationRequested)
    {
        return;
    }
   var obj = list[i];
   obj.TranslatedText = await Task.Run(() => Translator.Translate(obj.EnglishText, "English", File.Lang));
   progress.Report(i + 1);
   await Task.Delay(DELAY);
}

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2016-06-16
  • 1970-01-01
  • 2021-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-24
相关资源
最近更新 更多