CA1851:可能多次枚举了 IEnumerable 集合

属性
规则 ID CA1851
标题 可能多次枚举了 IEnumerable 集合
类别 “性能”
修复是中断修复还是非中断修复 非中断
引入的版本 .NET 7
在 .NET 8 中默认启用

原因

检测到集合 IEnumerable 的多次枚举。

规则说明

IEnumerableIEnumerable<T> 类型的集合能够在生成枚举时延迟该枚举。 许多 LINQ 方法(例如 Select)使用延迟执行。 当集合传递到 LINQ 枚举方法(如 ElementAt)或在 for each 语句中使用时,枚举开始。 枚举结果不会像 Lazy 一样计算一次并缓存。

如果枚举操作本身成本高昂(例如,对数据库进行查询),那么多次枚举会对程序的性能有害。

如果枚举操作具有副作用,则多次枚举可能会导致 bug。

如何解决冲突

如果 IEnumerable 集合的基础类型是其他某个类型(例如 ListArray),则可以安全地将集合转换为其基础类型以修复诊断出的问题。

冲突

public void MyMethod(IEnumerable<int> input)
{
    var count = input.Count();
    foreach (var i in input) { }
}
Public Sub MyMethod(input As IEnumerable(Of Integer))
    Dim count = input.Count()
    For Each i In input
    Next
End Sub

修复:

public void MyMethod(IEnumerable<int> input)
{
    // If the underlying type of 'input' is List<int>
    var inputList = (List<int>)input;
    var count = inputList.Count();
    foreach (var i in inputList) { }
}
Public Sub MyMethod(input As IEnumerable(Of Integer))
    ' If the underlying type of 'input' is array
    Dim inputArray = CType(input, Integer())
    Dim count = inputArray.Count()
    For Each i In inputArray
    Next
End Sub

如果 IEnumerable 集合的基础类型使用基于迭代器的实现(例如,如果它是由 Select 等 LINQ 方法生成的或通过使用 yield 关键字生成的),你可以通过将集合具体化来修复冲突。 但是,这会分配额外的内存。

例如:

冲突

public void MyMethod()
{
    var someStrings = GetStrings().Select(i => string.Concat("Hello"));

    // It takes 2 * O(n) to complete 'Count()' and 'Last()' calls, where 'n' is the length of 'someStrings'.
    var count = someStrings.Count();
    var lastElement = someStrings.Last();
}
Public Sub MyMethod()
    Dim someStrings = GetStrings().Select(Function(i) String.Concat(i, "Hello"))

    ' It takes 2 * O(n) to complete 'Count()' and 'Last()' calls, where 'n' is the length of 'someStrings'.
    Dim count = someStrings.Count()
    Dim lastElement = someStrings.Last()
End Sub

修复:

public void MyMethod()
{
    var someStrings = GetStrings().Select(i => string.Concat("Hello"));
    // Materialize it into an array.
    // Note: This operation would allocate O(n) memory,
    // and it takes O(n) time to finish, where 'n' is the length of 'someStrings'.
    var someStringsArray = someStrings.ToArray()

    // It takes 2 * O(1) to complete 'Count()' and 'Last()' calls.
    var count = someStringsArray.Count();
    var lastElement = someStringsArray.Last();
}
Public Sub MyMethod()
    Dim someStrings = GetStrings().Select(Function(i) String.Concat(i, "Hello"))
    ' Materialize it into an array.
    ' Note: This operation would allocate O(n) memory,
    ' and it takes O(n) time to finish, where 'n' is the length of 'someStrings'.
    Dim someStringsArray = someStrings.ToArray()

    ' It takes 2 * O(1) to complete 'Count()' and 'Last()' calls.
    Dim count = someStrings.Count()
    Dim lastElement = someStrings.Last()
End Sub

配置自定义枚举方法和 LINQ 链方法

默认情况下,System.Linq 命名空间中的所有方法都包含在分析范围中。 通过在 .editorconfig 文件中设置 enumeration_methods 选项,可以将枚举 IEnumerable 参数的自定义方法添加到范围中。

还可以通过在 .editorconfig 文件中设置 linq_chain_methods 选项,将自定义的 LINQ 链方法(即,方法采用 IEnumerable 参数并返回新的 IEnumerable 实例)添加到分析范围。

配置采用 IEnumerable 参数的方法的默认假设

默认情况下,所有接受 IEnumerable 参数的自定义方法都被假定不枚举该参数。 可以通过在 .editorconfig 文件中设置 assume_method_enumerates_parameters 选项来更改此设置。

何时禁止显示警告

如果 IEnumerable 集合的基础类型是其他某个类型(例如 ListArray),或者你确定采用 IEnumerable 集合的方法不会枚举它,则可以放心地禁止显示此警告。

抑制警告

如果只想抑制单个冲突,请将预处理器指令添加到源文件以禁用该规则,然后重新启用该规则。

#pragma warning disable CA1851
// The code that's violating the rule is on this line.
#pragma warning restore CA1851

若要对文件、文件夹或项目禁用该规则,请在配置文件中将其严重性设置为 none

[*.{cs,vb}]
dotnet_diagnostic.CA1851.severity = none

有关详细信息,请参阅如何禁止显示代码分析警告

另请参阅