在循环内修改元素会扰乱 for 循环
Modifying element inside a loop messes with the for loop
我正在使用以下 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 次 运行。
我试图用等效的 foreach
循环替换 for
循环:
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(() => Translator.Translate(obj.EnglishText, "English", File.Lang));
从第一个示例来看,它执行得很好。
我不能修改 IEnumerable 的内容吗?只是好奇。
更新 1
我在下面发布了一个可重现的示例。
现在我们有了一个完整的例子,大家可以看看问题出在哪里了。您用于 items
的集合是一个依赖于 TranslatedText
:
的查询
var source = collection.Where(x => string.IsNullOrEmpty(x.TranslatedText));
您对 obj
采取的操作会使该查询的结果无效,对于您正在处理的项目:
obj.TranslatedText = "something";
所以在你的 for
循环中,最初所有 10 个 Translation
对象都满足条件,所以 Count()
是 10。在循环的第一次迭代中,你访问第一个元素(元素 0),并将 obj.TranslatedText
设置为 "something"
.
现在,在循环的 每 次迭代中,您正在计算“查询的当前结果” - 现在是 9。然后您将按索引访问元素在查询的当前结果中 - 所以当 i
为 1 时,您将跳过查询的第一个匹配项并访问第二个匹配项。但这不是原始集合中的第二个匹配项 - 它是当前查询 的第二个匹配项 ,它已经跳过了第一个元素,因为您修改了它以设置翻译。因此,原始元素索引 1 在循环的第二次迭代中被跳过,您改为设置原始元素索引 2 的翻译文本。然后Count()
变成8,等等
使用 foreach
循环,您只对查询进行一次迭代 - 当您仍然使“您正在查看的当前元素”的查询条件无效时,查询处理不会无论如何都需要再次检查。
所以要么使用 foreach
循环,要么如果你想通过索引访问元素,你应该先 materialize 查询。例如,您可以使用:
// 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);
}
我正在使用以下 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 次 运行。
我试图用等效的 foreach
循环替换 for
循环:
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(() => Translator.Translate(obj.EnglishText, "English", File.Lang));
从第一个示例来看,它执行得很好。
我不能修改 IEnumerable 的内容吗?只是好奇。
更新 1 我在下面发布了一个可重现的示例。
现在我们有了一个完整的例子,大家可以看看问题出在哪里了。您用于 items
的集合是一个依赖于 TranslatedText
:
var source = collection.Where(x => string.IsNullOrEmpty(x.TranslatedText));
您对 obj
采取的操作会使该查询的结果无效,对于您正在处理的项目:
obj.TranslatedText = "something";
所以在你的 for
循环中,最初所有 10 个 Translation
对象都满足条件,所以 Count()
是 10。在循环的第一次迭代中,你访问第一个元素(元素 0),并将 obj.TranslatedText
设置为 "something"
.
现在,在循环的 每 次迭代中,您正在计算“查询的当前结果” - 现在是 9。然后您将按索引访问元素在查询的当前结果中 - 所以当 i
为 1 时,您将跳过查询的第一个匹配项并访问第二个匹配项。但这不是原始集合中的第二个匹配项 - 它是当前查询 的第二个匹配项 ,它已经跳过了第一个元素,因为您修改了它以设置翻译。因此,原始元素索引 1 在循环的第二次迭代中被跳过,您改为设置原始元素索引 2 的翻译文本。然后Count()
变成8,等等
使用 foreach
循环,您只对查询进行一次迭代 - 当您仍然使“您正在查看的当前元素”的查询条件无效时,查询处理不会无论如何都需要再次检查。
所以要么使用 foreach
循环,要么如果你想通过索引访问元素,你应该先 materialize 查询。例如,您可以使用:
// 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);
}