次の方法で共有


CA1851: IEnumerableコレクションの複数の列挙体が考えられます

プロパティ
ルール ID CA1851
Title IEnumerable コレクションの複数の列挙体が考えられます
[カテゴリ] パフォーマンス
修正が中断ありか中断なしか なし
導入されたバージョン .NET 7
.NET 9 では既定で有効 いいえ

原因

IEnumerable コレクションの複数列挙が検出されました。

規則の説明

IEnumerable または IEnumerable<T> のコレクションでは、生成時、列挙を先送りできます。 Select など、さまざまな LINQ メソッドでは遅延実行が使用されます。 列挙は、コレクションが ElementAt などの LINQ 列挙メソッドに渡されたときか、for each ステートメントで使用されたときに開始されます。 列挙結果は 1 回計算されるのではなく Lazy のようにキャッシュされます。

列挙操作自体の負荷が高い場合 (たとえば、データベースへのクエリなど)、複数の列挙がプログラムのパフォーマンスに悪影響を及ぼす可能性があります。

列挙操作に副作用がある場合は、複数の列挙がバグになる可能性があります。

違反の修正方法

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 名前空間内のすべてのメソッドが分析スコープに含まれます。 IEnumerable 引数を列挙するカスタム メソッドをスコープに追加するには、.editorconfig ファイルで enumeration_methods オプションを設定します。

.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

詳細については、「コード分析の警告を抑制する方法」を参照してください。

こちらもご覧ください