CA1851: diversas enumerações da coleção IEnumerable são possíveis

Property Valor
ID da regra CA1851
Título Possíveis enumerações múltiplas da coleção IEnumerable
Categoria Desempenho
Correção interruptiva ou sem interrupção Sem interrupção
Versão introduzida .NET 7
Habilitado por padrão no .NET 8 Não

Causa

Detectadas várias enumerações de uma coleção IEnumerable.

Descrição da regra

Uma coleção do tipo IEnumerable ou IEnumerable<T> tem a capacidade de adiar a enumeração quando ela é gerada. Muitos métodos LINQ, como Select, usam a execução adiada. A enumeração começa quando a coleção é passada para um método LINQ, como ElementAt, ou usada em um método para cada instrução. O resultado da enumeração não é calculado uma vez e armazenado em um cache, como Lazy.

Se a operação de enumeração em si for cara, por exemplo, uma consulta em um banco de dados, enumerações múltiplas serão prejudiciais ao desempenho do programa.

Se a operação de enumeração tiver efeitos colaterais, enumerações múltiplas poderão resultar em bugs.

Como corrigir violações

Se o tipo subjacente de sua coleção IEnumerable for outro, como List ou Array, será seguro converter a coleção em seu tipo subjacente para corrigir o diagnóstico.

Violação:

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

Correção:

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

Se o tipo subjacente da coleção IEnumerable usar uma implementação baseada em iterador (por exemplo, se ela for gerada por métodos LINQ como Select ou usando a palavra-chave yield ), você poderá corrigir a violação materializando a coleção. No entanto, isso aloca memória extra.

Por exemplo:

Violação:

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

Correção:

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

Configurar métodos de enumeração personalizados e métodos de cadeia LINQ

Por padrão, todos os métodos no namespace System.Linq são incluídos no escopo de análise. Você pode adicionar métodos personalizados que enumeram um argumento IEnumerable no escopo definindo a opção enumeration_methods em um arquivo .editorconfig.

Você também pode adicionar métodos de cadeia LINQ personalizados (ou seja, os métodos pegam um argumento IEnumerable e retornam uma nova instância IEnumerable) ao escopo de análise definindo a opção linq_chain_methods em um arquivo .editorconfig.

Configurar a suposição padrão de que métodos usam parâmetros IEnumerable

Por padrão, presumem-se que os métodos personalizados que aceitam um argumento IEnumerable não enumeram o argumento. Você pode alterar isso definindo a opção assume_method_enumerates_parameters em um arquivo .editorconfig.

Quando suprimir avisos

Será seguro suprimir esse aviso se o tipo subjacente da coleção IEnumerable for algum outro tipo como List ou Array, ou se você tiver certeza de que os métodos que usam uma coleção IEnumerable não o enumeram.

Suprimir um aviso

Para suprimir apenas uma violação, adicione diretivas de pré-processador ao arquivo de origem a fim de desabilitar e, em seguida, reabilitar a regra.

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

Para desabilitar a regra em um arquivo, uma pasta ou um projeto, defina a severidade como none no arquivo de configuração.

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

Para obter mais informações, confira Como suprimir avisos de análise de código.

Confira também