多次迭代错误¶
ID: cs/linq/inconsistent-enumeration
Kind: problem
Security severity:
Severity: warning
Precision: medium
Tags:
- reliability
- maintainability
- language-features
- external/cwe/cwe-834
Query suites:
- csharp-security-and-quality.qls
C# IEnumerable
可以被认为是一个延迟生成的元素序列。虽然 IEnumerable
通常由 List
或其他可以多次迭代的集合支持,但不能保证引用的序列始终是可重复的 - 如果我们多次迭代 IEnumerable
,每次都可能得到不同的结果。
建议¶
如果需要多次迭代序列,则必须确保它是可重复的,并且应该更改序列的类型以确保强制执行此约束。一种常见的方法(尽管并非在所有情况下都适用)是在创建序列时对序列调用 LINQ 的 ToList
方法,然后将其分配给具有适当元素类型的 IList
。由于可以安全地多次迭代列表,因此这解决了问题。但是,执行此操作时必须小心 - 对于 File.ReadLines
之类的操作,返回的序列是故意延迟的(堆空间可能不足以一次将整个文件保存在内存中),因此调用 ToList
会产生其他问题。在这种情况下,您可能需要重新设计代码的工作方式,以保持延迟行为,同时避免多次迭代。
作为一个具体的例子,请考虑需要在将延迟序列与另一个序列压缩之前检查它是否包含任何元素的情况 - 在这种情况下,一种合适的技术是编写一个包装器,尝试使用序列中的一个元素(以检查它是否为空),然后返回一个生成已使用元素以及序列其余部分的序列,或者返回 null。通过根据 null 检查结果,可以测试原始序列是否为空;如果它不为空,则可以使用新序列按需生成原始序列的所有元素。
示例¶
例如,下面示例中的方法 NonRepeatable
生成一个不可重复的三个整数序列。对 nr
的第二个循环不执行任何操作,因为不可重复的序列已被使用。尽管此示例有些刻意,但不可重复的序列并不少见 - 特别是,C# 库方法(例如 File.ReadLines
)会返回此类序列。因此,多次迭代未知的 IEnumerable
是不安全的,因为您无法确定它是否引用了可重复的序列。
class BadMultipleIteration
{
private static int count = 1;
private static IEnumerable<int> NonRepeatable()
{
for (; count <= 3; count++)
{
yield return count;
}
}
public static void Main(string[] args)
{
IEnumerable<int> nr = NonRepeatable();
foreach (int i in nr)
Console.WriteLine(i);
foreach (int i in nr)
Console.WriteLine(i);
}
}
可以通过将枚举方法的结果存储为列表来使此示例按预期工作。
class BadMultipleIterationFix
{
private static int count = 1;
private static IEnumerable<int> NonRepeatable()
{
for (; count <= 3; count++)
{
yield return count;
}
}
public static void Main(string[] args)
{
IList<int> nr = NonRepeatable().ToList<int>();
foreach (int i in nr)
Console.WriteLine(i);
foreach (int i in nr)
Console.WriteLine(i);
}
}
参考¶
MSDN:IEnumerable 接口。
常见弱点枚举:CWE-834。