C#における開発において、列挙型であるenumは、定数の集合を論理的にグループ化し、コードの可読性や保守性を向上させるために欠かせない存在です。

曜日、ステータス、種類など、特定の選択肢の中から値を選ぶような場面で頻繁に利用されます。

しかし、プログラムの実装を進める中で、「定義されている列挙型の要素をすべてリストアップしたい」「UIのコンボボックスに列挙型の名称を一覧表示したい」といった、列挙型の全要素をループ処理(列挙)したい場面に遭遇することがあります。

C#ではforeach文と標準ライブラリの機能を組み合わせることで、これらの処理を非常にシンプルに記述することが可能です。

本記事では、C#のenumforeachでループ処理する基本的な手法から、最新の.NETにおける推奨される書き方、さらには実務で役立つ応用テクニックまで、テクニカルライターの視点で詳しく解説します。

Enumをforeachで処理する基本:Enum.GetValues

C#で列挙型のすべての値を取得する最も標準的な方法は、System.Enumクラスに用意されているEnum.GetValuesメソッドを使用することです。

このメソッドは、指定した列挙型の定数値を含む配列を返します。

Enum.GetValuesの基本的な使い方

まずは、従来の書き方である非ジェネリック版のEnum.GetValuesを使用した例を見てみましょう。

このメソッドは引数にtypeof(列挙型名)を渡します。

C#
using System;

public enum UserStatus
{
    Active,
    Inactive,
    Pending,
    Suspended
}

class Program
{
    static void Main()
    {
        // Enum.GetValuesを使用して列挙型のすべての値を取得
        // 非ジェネリック版は Array 型を返すため、foreach内で型を指定する
        foreach (UserStatus status in Enum.GetValues(typeof(UserStatus)))
        {
            Console.WriteLine($"Status Value: {status}, Int Value: {(int)status}");
        }
    }
}
実行結果
Status Value: Active, Int Value: 0
Status Value: Inactive, Int Value: 1
Status Value: Pending, Int Value: 2
Status Value: Suspended, Int Value: 3

この方法では、Enum.GetValuesArrayクラス(中身は指定した列挙型の配列)を返すため、foreachの反復変数で明示的に型を指定することで、各要素にアクセスできます。

モダンな書き方:Enum.GetValues<T>

.NET 5以降の環境(最新の.NET 8や.NET 9を含む)では、ジェネリック版の Enum.GetValues<T> メソッドが推奨されます。

このメソッドを使用すると、戻り値が最初から特定の列挙型の型付き配列(T[])になるため、キャストのオーバーヘッドが減り、コードもより型安全でクリーンになります。

C#
using System;

public enum LogLevel
{
    Debug,
    Info,
    Warning,
    Error
}

class Program
{
    static void Main()
    {
        // ジェネリック版を使用することで、型推論(var)も利用可能
        var levels = Enum.GetValues<LogLevel>();

        foreach (var level in levels)
        {
            Console.WriteLine($"Log Level: {level}");
        }
    }
}
実行結果
Log Level: Debug
Log Level: Info
Log Level: Warning
Log Level: Error

Enum.GetValues<T>を使用するメリットは、実行時のパフォーマンス向上だけでなく、IDEのインテリセンスが適切に働き、開発時のミスを減らせる点にあります。

特別な理由がない限り、現代のC#開発ではこちらのジェネリック版を利用しましょう。

列挙型の「名称」をループで取得する方法

値そのものではなく、列挙型で定義されている「名前(文字列)」のリストを取得したい場合は、Enum.GetNamesメソッドを使用します。

Enum.GetNamesの使用例

UIのドロップダウンメニューに項目を表示する場合など、文字列の配列として取得したい場合に便利です。

C#
using System;

public enum Category
{
    Electronics,
    Books,
    Fashion,
    Home
}

class Program
{
    static void Main()
    {
        // 列挙型の名前(文字列)を配列として取得
        string[] categories = Enum.GetNames<Category>();

        foreach (string name in categories)
        {
            Console.WriteLine($"Category Name: {name}");
        }
    }
}
実行結果
Category Name: Electronics
Category Name: Books
Category Name: Fashion
Category Name: Home

Enum.GetValuesで取得した値をToString()しても同じ結果が得られますが、名前だけが必要な場合は Enum.GetNames を使用するほうが意図が明確になります。

LINQと組み合わせて列挙型を操作する

列挙型の要素をループ処理する際、特定の条件でフィルタリングしたり、別の型に変換したりしたい場合があります。

このようなケースでは、LINQ(Language Integrated Query)との組み合わせが非常に強力です。

特定の条件で抽出する例

例えば、特定の数値以上の値を持つ列挙メンバーだけを抽出したい場合のコードは以下のようになります。

C#
using System;
using System.Linq;

public enum TaskPriority
{
    Low = 10,
    Medium = 20,
    High = 30,
    Urgent = 40
}

class Program
{
    static void Main()
    {
        // 優先度が25以上のものだけを抽出してループ
        var highPriorities = Enum.GetValues<TaskPriority>()
                                 .Where(p => (int)p >= 25);

        foreach (var priority in highPriorities)
        {
            Console.WriteLine($"High Priority Task: {priority}");
        }
    }
}
実行結果
High Priority Task: High
High Priority Task: Urgent

LINQを使用することで、foreach文の中にif文を記述してフィルタリングする手間が省け、宣言的で読みやすいコードになります。

応用:Description属性を活用した表示名の取得

実務的なアプリケーション開発において、列挙型の識別子名(例:InStock)をそのまま画面に表示するのではなく、ユーザーに分かりやすい日本語名(例:在庫あり)を表示したいことが多々あります。

この場合、System.ComponentModel.DescriptionAttributeを使用して、列挙型の各メンバーに説明文を付与し、ループ処理の中でその属性値を取得する手法が一般的です。

属性値を取得するループ処理の実装

属性を取得するにはリフレクションを利用するため、少し複雑な処理が必要になりますが、一度ユーティリティメソッドを作っておけば非常に便利です。

C#
using System;
using System.ComponentModel;
using System.Reflection;

public enum OrderStatus
{
    [Description("受注待ち")]
    AwaitingPayment,

    [Description("出荷準備中")]
    Processing,

    [Description("発送済み")]
    Shipped,

    [Description("完了")]
    Delivered
}

class Program
{
    static void Main()
    {
        foreach (OrderStatus status in Enum.GetValues<OrderStatus>())
        {
            string description = GetEnumDescription(status);
            Console.WriteLine($"値: {status} -> 表示名: {description}");
        }
    }

    // 列挙型のDescription属性を取得するメソッド
    static string GetEnumDescription(Enum value)
    {
        FieldInfo field = value.GetType().GetField(value.ToString());
        DescriptionAttribute attribute = field.GetCustomAttribute<DescriptionAttribute>();

        return attribute == null ? value.ToString() : attribute.Description;
    }
}
実行結果
値: AwaitingPayment -> 表示名: 受注待ち
値: Processing -> 表示名: 出荷準備中
値: Shipped -> 表示名: 発送済み
値: Delivered -> 表示名: 完了

このように、foreachループとリフレクションを組み合わせることで、コード上の定数名とエンドユーザー向けの表示名をスマートに管理できるようになります。

Flags属性(ビットフラグ)を持つEnumのループ処理

列挙型に[Flags]属性が付与されている場合、複数の状態をひとつの変数で保持できます。

この「フラグ」として扱われる列挙型をループ処理する場合、通常のEnum.GetValuesでは「定義されている個別の要素」が返されます。

特定の変数が持っているフラグを個別に判定してループしたい場合は、以下のように記述します。

C#
using System;

[Flags]
public enum Permissions
{
    None = 0,
    Read = 1,
    Write = 2,
    Execute = 4,
    Admin = Read | Write | Execute
}

class Program
{
    static void Main()
    {
        // ユーザーが持っている権限(複合フラグ)
        Permissions myPermissions = Permissions.Read | Permissions.Execute;

        Console.WriteLine($"現在の権限: {myPermissions}");

        // 定義されている各フラグをチェック
        foreach (Permissions p in Enum.GetValues<Permissions>())
        {
            // None(0)を除外し、変数がそのフラグを持っているか判定
            if (p != Permissions.None && myPermissions.HasFlag(p))
            {
                Console.WriteLine($"有効な権限を検出: {p}");
            }
        }
    }
}
実行結果
現在の権限: Read, Execute
有効な権限を検出: Read
有効な権限を検出: Execute

Flags属性を使用する場合、Enum.GetValuesは「Admin」のような複合値も一つの要素として返してしまう点に注意が必要です。

純粋なビットごとの要素だけを取り出したい場合は、ビット演算を用いたフィルタリングが必要になることもあります。

パフォーマンス上の注意点と最適化

Enum.GetValuesEnum.GetNamesは非常に便利なメソッドですが、内部的にはリフレクション(Reflection)を使用しています。

そのため、極端にパフォーマンスが要求されるループの中で頻繁に呼び出すと、実行速度に影響を与える可能性があります。

最適化のポイント:結果のキャッシュ

同じ列挙型の要素を何度も繰り返しループ処理する必要がある場合は、静的な読み取り専用フィールドなどに結果をキャッシュ(保持)しておくのがベストプラクティスです。

C#
public static class EnumCache<T> where T : struct, Enum
{
    // 静的コンストラクタで一度だけ取得されるため、高速にアクセス可能
    public static readonly T[] Values = Enum.GetValues<T>();
}

// 利用時
foreach (var value in EnumCache<MyEnum>.Values)
{
    // 高速な処理が可能
}

このようにジェネリックなキャッシュクラスを作成しておくことで、リフレクションの呼び出し回数を最小限に抑えつつ、簡潔なコードを維持できます。

まとめ

C#でenumforeachでループ処理する方法について解説しました。

本記事の要点をまとめると以下の通りです。

手法特徴・用途
Enum.GetValues<T>モダンな.NETにおける標準的な手法。型安全に全ての値を取得できる。
Enum.GetNames<T>列挙型の名前(文字列)を配列として取得。UI表示等に便利。
LINQとの連携WhereSelectを使い、条件抽出や型変換をスマートに行う。
Description属性リフレクションを使い、ユーザー向けの日本語表示名などを取得する。
キャッシュの活用頻繁に呼び出す場合は、配列を静的変数に保持してパフォーマンスを改善する。

列挙型のループ処理は、設定画面の生成、データのバリデーション、ログ出力の自動化など、多岐にわたるシーンで活用されます。

特に.NET 5以降で利用可能になったジェネリック版のメソッドは、型安全性の観点からも積極的に取り入れていきたい機能です。

これらの手法を適切に使い分けることで、より堅牢でメンテナンス性の高いC#プログラムを記述できるようになります。

今回紹介したテクニックを、ぜひ日々の開発業務に役立ててください。