LINQでシーケンスが空か、一定数の要素が存在するか確認する方法

LINQでシーケンスが空かどうかの確認、一定数以上の要素が存在するかどうかの確認方法について、パフォーマンスを考慮して考察します。

言語

前提

IEnumerableインタフェースを介してシーケンスが空かどうかの確認、一定数以上の要素が存在するかどうかの確認方法について考察します。 シーケンスの実体からICollection<T>.Countプロパティなどにアクセスできるのであれば、それらを使用する方がパフォーマンス的に優位な可能性がありますが、この記事では触れません。

シーケンスが空か確認する

Any関数でシーケンスが空か確認する

通常、LINQでシーケンスが空かどうか確認するには、引数なしのAny関数を使用して、Any(1個以上存在する)の否定で確認します。

static void Example(IEnumerable<int> enumerable)
{
    if (!enumerable.Any())
        Console.WriteLine("enumerable は空です。");
}

Any関数は、実際にシーケンスから最初の要素を取得できるかどうかで判定を行います。 そのため、例えば、次のコードのようにシーケンスが空ではないことを確認してから要素を取得するような使い方をすると、シーケンスの最初の要素を2回取得することになります。

static void Example(IEnumerable<int> enumerable)
{
    if (enumerable.Any())
        Console.WriteLine(enumerable.First());
}

このことは、要素の取得コストが高いシーケンスを扱うロジックでは留意が必要です。

Count関数でシーケンスが空か確認する

LINQのCount関数の戻り値を0と比較してシーケンスが空かどうかを判定することができます。 ただ、後述する通り、この使い方が適切なケースは限られると思います。

static void Example(IEnumerable<int> enumerable)
{
    if (enumerable.Count() == 0)
        Console.WriteLine("enumerable は空です。");
}

Count関数は、要素の数を数えるためにすべての要素を取得してカウントした値を返します。 純粋にシーケンスが空かどうかを判定したいだけであれば、2つ目以降の要素を取得する必要がないためAny関数を使うのが適切だと思います。

Count関数が有効なケースは、その振る舞いの通り、シーケンス全体を走査することが目的で、結果として要素が存在したかどうかを知りたい場合だと思います。

シーケンスをただ走査して意味があるのかですが、通常の配列やコレクションではそれ以上の意味はありませんが、IEnumerableの実装はネットワークからの応答やロジックで動的に生成した値を返すことも可能なため、シチュエーションは限られますがそのような(取得した要素を使わないとしてもシーケンスを走査することで副作用を引き起こす)ロジックが必要なケースは考えられます。

シーケンスにN個以上の要素が存在するか確認する

LINQのTake関数とCount関数を組み合わせることで、シーケンスにN個以上の要素が存在するかどうかを必要最小限の要素を取得して判定できます。

static void Example(IEnumerable<int> enumerable, int n)
{
    if (enumerable.Take(n).Count() == n)
        Console.WriteLine($"enumerable は {n} 個以上の要素を持っています。");
}

Count関数は、そのままでは確認したい要素数を超えて全量を処理してしまいます。 そこで、Take関数を使い取得する要素数を制限した上で、Count関数を使うことで必要最小限の処理で判定が行えます。