我正在使用以下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(() => Translator.Translate(obj.EnglishText, "English", File.Lang));
在第一个示例中,它执行得很好。
我不允许修改IEnumerable的内容吗?只是好奇。
更新1 我在下面发布了一个可复制的示例。
答案 0 :(得分:2)
现在我们有一个完整的示例,我们可以看到问题所在。您用于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
循环,或者如果要按索引访问元素,则应首先具体化查询。例如,您可以使用:
// 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);
}