CA1851: Possible multiple enumerations of IEnumerable collection

Property Value
Rule ID CA1851
Title Possible multiple enumerations of IEnumerable collection
Category Performance
Fix is breaking or non-breaking Non-breaking
Introduced version .NET 7
Enabled by default in .NET 8 No

Cause

Detected multiple enumerations of an IEnumerable collection.

Rule description

A collection of type IEnumerable or IEnumerable<T> has the ability to defer enumeration when it's generated. Many LINQ methods, such as Select, use deferred execution. Enumeration starts when the collection is passed into a LINQ enumeration method, like ElementAt, or used in a for each statement. The enumeration result is not calculated once and cached, like Lazy.

If the enumeration operation itself is expensive, for example, a query into a database, multiple enumerations would hurt the performance of the program.

If the enumeration operation has side effects, multiple enumerations might result in bugs.

How to fix violations

If the underlying type of your IEnumerable collection is some other type, such as List or Array, it's safe to convert the collection to its underlying type to fix the diagnostic.

Violation:

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

Fix:

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

If the underlying type of the IEnumerable collection uses an iterator-based implementation (for example, if it's generated by LINQ methods like Select or by using the yield keyword), you can fix the violation by materializing the collection. However, this allocates extra memory.

For example:

Violation:

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

Fix:

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

Configure customized enumeration methods and LINQ chain methods

By default, all the methods in the System.Linq namespace are included in the analysis scope. You can add custom methods that enumerate an IEnumerable argument into the scope by setting the enumeration_methods option in an .editorconfig file.

You can also add customized LINQ chain methods (that is, methods take an IEnumerable argument and return a new IEnumerable instance) to the analysis scope by setting the linq_chain_methods option in an .editorconfig file.

Configure the default assumption of methods take IEnumerable parameters

By default, all customized methods that accept an IEnumerable argument are assumed not to enumerate the argument. You can change this by setting the assume_method_enumerates_parameters option in an .editorconfig file.

When to suppress warnings

It's safe to suppress this warning if the underlying type of the IEnumerable collection is some other type like List or Array, or if you're sure that methods that take an IEnumerable collection don't enumerate it.

Suppress a warning

If you just want to suppress a single violation, add preprocessor directives to your source file to disable and then re-enable the rule.

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

To disable the rule for a file, folder, or project, set its severity to none in the configuration file.

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

For more information, see How to suppress code analysis warnings.

See also