C#を用いたアプリケーション開発において、配列やリストといったコレクションの要素を順に処理するforeach文は、最も頻繁に利用される構文の一つです。

しかし、特定の条件に合致する要素だけを処理から除外したい、あるいは特定のデータのみをスキップして次のループに進みたいという場面は多々あります。

このような制御フローを実現するために欠かせないのがcontinueステートメントです。

本記事では、C#のforeachループにおけるcontinueの基本的な使い方から、実戦的な活用シーン、さらにはコードの可読性を高めるためのテクニックについて、プロフェッショナルな視点で詳しく解説します。

C#におけるforeach文とcontinueの基本概念

C#のforeach文は、IEnumerableインターフェースを実装したコレクション(配列、List、Dictionaryなど)の各要素に対して、反復処理を行うための構文です。

通常、ループ内の処理は上から下へと順番に実行されますが、特定の条件で現在の反復を中断し、即座に次の要素の処理に移りたい場合があります。

この役割を担うのがcontinueです。

continueが実行されると、それ以降のループ内に記述されたコードは実行されず、制御がループの先頭(次の要素の取り出し)に戻ります。

continueの基本的な構文

まずは、もっともシンプルな例を見てみましょう。

以下のコードは、数値のリストから偶数のみをスキップし、奇数だけを表示するプログラムです。

C#
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        Console.WriteLine("奇数のリストを表示します:");

        foreach (int number in numbers)
        {
            // 数値が偶数の場合、現在のループをスキップする
            if (number % 2 == 0)
            {
                continue; // これ以降の処理は行われず、次のループへ
            }

            // 奇数の場合のみ、この行が実行される
            Console.WriteLine($"数値: {number}");
        }
    }
}
実行結果
奇数のリストを表示します:
数値: 1
数値: 3
数値: 5
数値: 7
数値: 9

この例では、number % 2 == 0という条件が真(True)になった瞬間にcontinueが呼ばれます。

その結果、直後のConsole.WriteLineは実行されず、次の数値の判定へと進みます。

foreachでcontinueを活用するメリット

なぜcontinueを使う必要があるのでしょうか。

単純にif文で処理全体を囲むことでも同じ動作は実現できます。

しかし、continueを効果的に使うことで、コードの品質を大幅に向上させることが可能です。

1. ネストの深さを抑制する(ガード句の役割)

大量の条件分岐がある場合、if文で正常系を囲み続けると、コードのインデント(下げ振り)が深くなりすぎてしまいます。

これを「階段コード」や「ピラミッドコード」と呼び、可読性を著しく低下させる要因となります。

continueガード句として使用することで、異常系やスキップ対象を先に除外し、メインのロジックを浅いネストで記述できるようになります。

2. ロジックの分離が明確になる

「何を処理するか」よりも先に「何を処理しないか」を定義することで、ループの本質的な処理が際立ちます。

特にデータクレンジングやバリデーション(妥当性確認)を行う際に有効です。

実践的なユースケース

現場でよく遭遇する、continueを活用すべき具体的なシナリオを紹介します。

nullチェックと異常データの除外

外部APIやデータベースから取得したリストには、稀にnullや不正な形式のデータが混入することがあります。

これらを事前にスキップすることで、NullReferenceExceptionなどのランタイムエラーを未然に防ぎます。

C#
using System;
using System.Collections.Generic;

public class User
{
    public string Name { get; set; }
    public bool IsActive { get; set; }
}

class Program
{
    static void Main()
    {
        List<User> users = new List<User>
        {
            new User { Name = "Alice", IsActive = true },
            null, // 不正なデータ
            new User { Name = "Bob", IsActive = false },
            new User { Name = "Charlie", IsActive = true }
        };

        foreach (var user in users)
        {
            // 1. nullチェック
            if (user == null)
            {
                continue;
            }

            // 2. アクティブでないユーザーをスキップ
            if (!user.IsActive)
            {
                continue;
            }

            // メイン処理:アクティブなユーザーのみ名前を表示
            Console.WriteLine($"処理中のユーザー: {user.Name}");
        }
    }
}
実行結果
処理中のユーザー: Alice
処理中のユーザー: Charlie

try-catch内での利用

ループ内でエラーが発生した際、ループ全体を止めるのではなく、その要素の処理だけを諦めて次の要素に進みたい場合があります。

このようなケースでもcontinueが重宝されます。

C#
foreach (var file in files)
{
    try
    {
        // ファイルの読み込み処理(エラーの可能性がある)
        ProcessFile(file);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"エラーが発生したためスキップします: {file}, 理由: {ex.Message}");
        continue;
    }
}

continueとbreakの違い

よく混同されるのがbreakステートメントです。

両者の動作の違いを正しく理解しておくことは非常に重要です。

ステートメント動作主な用途
continue現在の反復のみを終了し、次の要素へ進む特定条件のデータを除外・スキップしたいとき
breakループ自体を即座に終了し、ループ外へ抜ける目的のデータが見つかったとき、以降の処理が不要なとき

例えば、リストの中に特定の検索対象を見つけた時点で処理を終わりたい場合はbreakを使いますが、不適切なデータを除いて全件チェックしたい場合はcontinueを使います。

入れ子(多重ループ)における挙動

foreachの中にさらに別のループがある場合、continue直近の(内側の)ループに対してのみ作用します。

C#
foreach (var category in categories)
{
    foreach (var item in category.Items)
    {
        if (item.IsSoldOut)
        {
            // 内側のループ(itemのループ)をスキップする
            continue;
        }
        // ... 商品の処理
    }
}

もし内側のループから外側のループをスキップさせたい場合は、フラグ変数を用意するか、LINQを活用してあらかじめデータをフィルタリングしておくなどの工夫が必要になります。

continueを使いこなすための注意点

非常に便利なcontinueですが、多用しすぎるとかえってプログラムの流れを追いづらくなる(スパゲッティコード化する)恐れがあります。

1. 条件が複雑になりすぎないようにする

if (condition1 || (condition2 && !condition3)) continue; のように複雑な条件を書くと、なぜスキップされているのかが直感的に理解できなくなります。

条件が複雑になる場合は、メソッド化して「スキップすべきかどうか」を判定するロジックを外に切り出すのが賢明です。

2. LINQのWhere句との使い分け

現代的なC#開発では、foreachcontinueを組み合わせる代わりに、LINQ(Language Integrated Query)を使用してあらかじめ要素を絞り込む手法が推奨されることも多いです。

C#
// continueを使うパターン
foreach (var item in items)
{
    if (item.Price <= 0) continue;
    Process(item);
}

// LINQを使うパターン
foreach (var item in items.Where(x => x.Price > 0))
{
    Process(item);
}

LINQを使うと「何に対して処理を行うのか」が宣言的に記述できるため、意図が伝わりやすくなります。

一方で、ループ内での状態管理が必要な場合や、デバッグのしやすさを優先する場合は、従来のforeach + continueが適していることもあります。

まとめ

C#のforeachにおけるcontinueは、ループ処理の制御を柔軟にし、コードの可読性を高めるための強力な武器です。

  • 基本:現在の要素の処理をスキップし、次の要素へ進める。
  • メリット:ガード句として使うことでネストを浅く保てる。
  • 使い分け:breakはループ終了、continueは反復のスキップ。
  • 最適化:処理対象が明確な場合は、LINQのWhere句の使用も検討する。

これらを意識して実装することで、バグが少なくメンテナンス性の高いC#プログラムを記述できるようになります。

特に大規模なデータ処理や複雑な業務ロジックを扱う際には、continueを適切に配置して、見通しの良いクリーンなコードを目指しましょう。