C# enumとは?定義・使い方・数値変換・文字列変換まで初心者向けに徹底解説
はじめに
C#でプログラムを作成していると、「注文状態」「ユーザー種別」「処理結果」など、あらかじめ決まった選択肢の中から1つの値を扱いたい場面があります。このような場合に役立つのが、C#のenum(列挙型)です。
たとえば、注文状態を数値で管理すると、次のようなコードになります。
C#int orderStatus = 1;
しかし、1だけを見ても、それが「受付済み」「発送済み」「キャンセル」のどれを表しているのか分かりません。そこでenumを使うと、次のように意味の分かる名前で状態を表現できます。
C#OrderStatus orderStatus = OrderStatus.Shipped;
enumを活用すれば、コードの可読性が高まり、入力ミスや値の取り違えも防ぎやすくなります。
この記事では、C#のenumの定義方法と基本的な使い方から、数値・文字列との変換、一覧取得、Flags属性、表示名の設定、JSONやデータベースで扱う際の注意点まで、初心者にも分かりやすく解説します。
1. C#のenum(列挙型)とは
1-1. enumの基本的な意味と役割
enumとは、関連する複数の定数を、1つの型としてまとめて定義するための仕組みです。「列挙型」とも呼ばれます。
たとえば、注文状態を表すenumは次のように定義できます。
C#public enum OrderStatus
{
Pending,
Paid,
Shipped,
Completed,
Canceled
}
この例では、OrderStatusという型の中に、次の5つの値を定義しています。
PendingPaidShippedCompletedCanceled
変数には、この中のいずれかを代入できます。
C#OrderStatus status = OrderStatus.Paid;
OrderStatus型の変数に、無関係な文字列や別のenum型をそのまま代入することはできません。これにより、扱える値の範囲を型によって明確にできます。
1-2. enumを使うメリット
enumを使う主なメリットは、コードの意味が分かりやすくなることです。
次のコードでは、数値だけで状態を管理しています。
C#if (status == 2)
{
Console.WriteLine("発送済みです");
}
このコードだけを見ても、なぜ2なら発送済みなのかが分かりません。このような意味の分かりにくい数値は、マジックナンバーと呼ばれます。
enumを使えば、次のように書けます。
C#if (status == OrderStatus.Shipped)
{
Console.WriteLine("発送済みです");
}
OrderStatus.Shippedという名前から、条件の意味をすぐに理解できます。
そのほかにも、enumには次のようなメリットがあります。
使用できる値を限定できる
IDEの入力補完を利用できる
スペルミスをコンパイル時に検出しやすい
switch文による分岐が分かりやすくなるメソッドの引数や戻り値の意味を明確にできる
数値や文字列を直接扱うよりも保守しやすい
1-3. 定数クラスや文字列で管理する場合との違い
状態を表す方法として、enum以外に定数クラスや文字列を使う方法があります。
定数クラスを使う例は次のとおりです。
C#public static class OrderStatuses
{
public const int Pending = 0;
public const int Paid = 1;
public const int Shipped = 2;
}
文字列で管理する場合は、次のようになります。
C#string status = "Shipped";
定数クラスや文字列でも値を表現できますが、enumと比べると型による制約が弱くなります。
たとえば、文字列では次のようなスペルミスがあっても、コンパイルエラーにはなりません。
C#string status = "Shiped";
enumなら、存在しないメンバーを指定するとコンパイルエラーになります。
C#OrderStatus status = OrderStatus.Shiped;
一方、定数クラスには文字列や小数、オブジェクトなど、enumでは扱えない値も定義できます。enumは、整数値を基礎とする固定的な選択肢を表現する場合に適しています。
1-4. enumが適しているケース・適していないケース
enumは、選択肢が少なく、プログラム内でほぼ固定されている場合に適しています。
代表的な例は次のとおりです。
注文状態
ユーザー権限
商品種別
曜日
支払い方法
ログレベル
処理結果
ファイル形式
反対に、次のようなデータにはenumが適していないことがあります。
データベースから頻繁に追加・削除される分類
管理画面でユーザーが自由に編集する選択肢
名前以外に多数の属性や振る舞いを持つデータ
実行時に種類が増えるプラグイン
外部サービス側で頻繁に変更される値
たとえば、商品カテゴリを管理画面から自由に追加できるシステムでは、enumではなくデータベースのテーブルとして管理するほうが適しています。
2. C#でenumを定義する方法
2-1. enumの基本構文
enumは、enumキーワードを使って定義します。
C#アクセス修飾子 enum 列挙型名
{
メンバー1,
メンバー2,
メンバー3
}
実際のコードは次のようになります。
C#public enum PaymentMethod
{
Cash,
CreditCard,
BankTransfer
}
一般的に、enumの型名とメンバー名にはPascalCaseを使用します。
C#public enum UserRole
{
Guest,
Member,
Administrator
}
2-2. enumのメンバーを宣言する方法
enumのメンバーは、波かっこの中にカンマ区切りで記述します。
C#public enum LogLevel
{
Trace,
Debug,
Information,
Warning,
Error,
Critical
}
末尾のメンバーにもカンマを付けられます。
C#public enum LogLevel
{
Trace,
Debug,
Information,
Warning,
Error,
Critical,
}
末尾にカンマを付けておくと、後からメンバーを追加したときに差分が分かりやすくなる場合があります。
2-3. enumのメンバーに数値を指定する方法
enumの各メンバーには整数値が割り当てられます。数値を指定しない場合、最初のメンバーは0となり、以降は1ずつ増加します。
C#public enum OrderStatus
{
Pending, // 0
Paid, // 1
Shipped, // 2
Completed // 3
}
数値を明示的に指定することもできます。
C#public enum OrderStatus
{
Pending = 0,
Paid = 10,
Shipped = 20,
Completed = 30
}
一部のメンバーだけに数値を指定した場合、その次のメンバーには直前の値に1を加えた数値が割り当てられます。
C#public enum StatusCode
{
None = 0,
Started = 10,
Processing, // 11
Completed // 12
}
データベースや外部APIと値を共有する場合は、メンバーの追加や並べ替えによって数値が変わらないよう、明示的に数値を指定するのが安全です。
2-4. enumの基になる整数型を指定する方法
enumの基になる型は、既定ではintです。
C#public enum OrderStatus
{
Pending,
Paid,
Shipped
}
これは、次のように定義した場合と同じです。
C#public enum OrderStatus : int
{
Pending,
Paid,
Shipped
}
基になる型には、次の整数型を指定できます。
bytesbyteshortushortintuintlongulong
たとえば、byteを基にする場合は次のように定義します。
C#public enum AccessLevel : byte
{
None = 0,
Read = 1,
Write = 2,
Administrator = 3
}
非常に大きな値を扱う場合は、longやulongを指定できます。
C#public enum LargeCode : long
{
None = 0,
ValueA = 10_000_000_000,
ValueB = 20_000_000_000
}
ただし、特別な理由がなければ、既定のintを使用するのが一般的です。
2-5. enumのアクセス修飾子と定義場所
名前空間の直下に定義するenumには、publicまたはinternalを指定できます。
C#public enum OrderStatus
{
Pending,
Paid,
Shipped
}
アクセス修飾子を省略した場合は、internalになります。
C#enum OrderStatus
{
Pending,
Paid,
Shipped
}
クラスや構造体の中に、入れ子のenumとして定義することもできます。
C#public class Order
{
public enum Status
{
Pending,
Paid,
Shipped
}
}
この場合は、次のように参照します。
C#Order.Status status = Order.Status.Paid;
入れ子のenumには、public、private、protected、internalなど、メンバーに使用できるアクセス修飾子を指定できます。
複数のクラスから利用するenumは名前空間の直下に定義し、特定のクラス内でしか使わないenumはクラス内に定義すると、用途が分かりやすくなります。
3. C#のenumの基本的な使い方
3-1. enum型の変数を宣言・代入する
enum型の変数は、通常の型と同じように宣言します。
C#OrderStatus status;
値を代入する場合は、列挙型名.メンバー名と記述します。
C#status = OrderStatus.Paid;
宣言と代入を同時に行うこともできます。
C#OrderStatus status = OrderStatus.Paid;
varを使用すると、右辺から型が推論されます。
C#var status = OrderStatus.Paid;
この場合も、statusの型はOrderStatusです。
3-2. if文でenumの値を判定する
enumの値は、==演算子で比較できます。
C#OrderStatus status = OrderStatus.Shipped;
if (status == OrderStatus.Shipped)
{
Console.WriteLine("商品は発送済みです。");
}
!=演算子を使った比較も可能です。
C#if (status != OrderStatus.Canceled)
{
Console.WriteLine("注文は有効です。");
}
複数の値を条件にする場合は、論理演算子を組み合わせます。
C#if (status == OrderStatus.Paid ||
status == OrderStatus.Shipped)
{
Console.WriteLine("支払いが完了しています。");
}
3-3. switch文でenumを分岐処理する
enumは、switch文やswitch式との相性が良い型です。
C#OrderStatus status = OrderStatus.Paid;
switch (status)
{
case OrderStatus.Pending:
Console.WriteLine("支払い待ちです。");
break;
case OrderStatus.Paid:
Console.WriteLine("入金済みです。");
break;
case OrderStatus.Shipped:
Console.WriteLine("発送済みです。");
break;
case OrderStatus.Completed:
Console.WriteLine("取引完了です。");
break;
case OrderStatus.Canceled:
Console.WriteLine("キャンセル済みです。");
break;
default:
Console.WriteLine("不明な状態です。");
break;
}
switch式を使うと、より簡潔に値を返せます。
C#string message = status switch
{
OrderStatus.Pending => "支払い待ちです。",
OrderStatus.Paid => "入金済みです。",
OrderStatus.Shipped => "発送済みです。",
OrderStatus.Completed => "取引完了です。",
OrderStatus.Canceled => "キャンセル済みです。",
_ => "不明な状態です。"
};
外部入力や数値変換によって未定義値が入る可能性を考慮し、必要に応じてdefaultや_を用意することが重要です。
3-4. メソッドの引数や戻り値にenumを使う
メソッドの引数にenumを指定すると、受け付ける値を明確にできます。
C#public static void UpdateStatus(OrderStatus status)
{
Console.WriteLine($"状態を{status}に変更しました。");
}
呼び出す側は、次のようにenum値を渡します。
C#UpdateStatus(OrderStatus.Shipped);
戻り値としてenumを返すこともできます。
C#public static OrderStatus GetInitialStatus()
{
return OrderStatus.Pending;
}
文字列や数値を返す場合よりも、戻り値として想定される選択肢が明確になります。
3-5. クラスのプロパティとしてenumを使う
enumは、クラスのプロパティにも使用できます。
C#public class Order
{
public int Id { get; set; }
public OrderStatus Status { get; set; }
}
オブジェクトを作成するときは、次のように値を設定します。
C#var order = new Order
{
Id = 1,
Status = OrderStatus.Paid
};
必須の値ではない場合は、nullable enumを使用できます。
C#public class OrderSearchCondition
{
public OrderStatus? Status { get; set; }
}
値が設定されているかは、HasValueやnullパターンで確認できます。
C#if (condition.Status.HasValue)
{
Console.WriteLine(condition.Status.Value);
}
C#if (condition.Status is OrderStatus status)
{
Console.WriteLine(status);
}
3-6. enum同士を比較する
同じenum型の値は、==、!=、Equalsで比較できます。
C#OrderStatus first = OrderStatus.Paid;
OrderStatus second = OrderStatus.Paid;
bool result1 = first == second;
bool result2 = first.Equals(second);
Console.WriteLine(result1); // True
Console.WriteLine(result2); // True
異なるenum型は、基になる数値が同じでも直接比較できません。
C#public enum OrderStatus
{
Pending = 0,
Paid = 1
}
public enum PaymentStatus
{
Unpaid = 0,
Paid = 1
}
次の比較はコンパイルエラーになります。
C#OrderStatus orderStatus = OrderStatus.Paid;
PaymentStatus paymentStatus = PaymentStatus.Paid;
// bool result = orderStatus == paymentStatus;
同じ数値を持っていても意味の異なる型として扱われるため、誤比較を防げます。
4. C#のenumを数値に変換する方法
4-1. enumをint型にキャストする
基になる型がintのenumは、明示的なキャストでintに変換できます。
C#public enum OrderStatus
{
Pending = 0,
Paid = 10,
Shipped = 20
}
OrderStatus status = OrderStatus.Shipped;
int value = (int)status;
Console.WriteLine(value); // 20
暗黙的には変換されないため、次のコードはコンパイルエラーになります。
C#// int value = status;
必ず(int)を指定します。
4-2. int型の数値をenumにキャストする
intからenumへの変換も、明示的なキャストを使います。
C#int value = 20;
OrderStatus status = (OrderStatus)value;
Console.WriteLine(status); // Shipped
ただし、enumに定義されていない数値でもキャスト自体は成功します。
C#int value = 999;
OrderStatus status = (OrderStatus)value;
Console.WriteLine(status); // 999
C#のenumは、定義されたメンバーだけを保持できる厳密な制限型ではありません。基になる整数型の範囲内であれば、未定義の数値も保持できる点に注意が必要です。
4-3. int以外の整数型とenumを相互変換する
基になる型がbyteの場合は、byteとの間でキャストできます。
C#public enum AccessLevel : byte
{
None = 0,
Read = 1,
Write = 2
}
AccessLevel level = AccessLevel.Write;
byte value = (byte)level;
Console.WriteLine(value); // 2
byteからenumへの変換は次のとおりです。
C#byte value = 1;
AccessLevel level = (AccessLevel)value;
Console.WriteLine(level); // Read
基になる型がlongの場合も同様です。
C#public enum LargeStatus : long
{
None = 0,
Active = 10_000_000_000
}
LargeStatus status = LargeStatus.Active;
long value = (long)status;
基になる型と異なる整数型へ変換する場合は、値の範囲を確認する必要があります。
C#long longValue = (long)status;
int intValue = checked((int)longValue);
checkedを使用すると、変換先の範囲を超えた場合にOverflowExceptionが発生します。
4-4. Enum.ToObjectメソッドで数値をenumに変換する
Enum.ToObjectメソッドを使って、数値からenumのオブジェクトを作成できます。
C#int value = 20;
object result = Enum.ToObject(typeof(OrderStatus), value);
OrderStatus status = (OrderStatus)result;
Console.WriteLine(status); // Shipped
ジェネリックメソッドの中など、enum型をTypeオブジェクトとして扱っている場合に便利です。
C#public static object ConvertToEnum(Type enumType, int value)
{
if (!enumType.IsEnum)
{
throw new ArgumentException("enum型を指定してください。", nameof(enumType));
}
return Enum.ToObject(enumType, value);
}
通常のコードで変換先のenum型が分かっている場合は、単純なキャストのほうが読みやすいでしょう。
4-5. Enum.IsDefinedメソッドで有効な数値か確認する
数値がenumに定義されているか確認するには、Enum.IsDefinedメソッドを使用します。
C#int value = 20;
if (Enum.IsDefined(typeof(OrderStatus), value))
{
OrderStatus status = (OrderStatus)value;
Console.WriteLine(status);
}
else
{
Console.WriteLine("定義されていない値です。");
}
ジェネリック版を利用できる環境では、次のようにも記述できます。
C#if (Enum.IsDefined((OrderStatus)value))
{
OrderStatus status = (OrderStatus)value;
}
値を変換するだけならキャストで十分ですが、ユーザー入力、API、データベースなど、外部から取得した数値を扱う場合は、事前に定義済みか確認すると安全です。
4-6. 未定義の数値をenumに変換するときの注意点
未定義値のキャストは例外になりません。
C#OrderStatus status = (OrderStatus)999;
そのため、次のようなコードでは、想定外の値がそのまま処理される可能性があります。
C#public static void ProcessStatus(int value)
{
OrderStatus status = (OrderStatus)value;
switch (status)
{
case OrderStatus.Pending:
Console.WriteLine("支払い待ち");
break;
case OrderStatus.Paid:
Console.WriteLine("入金済み");
break;
case OrderStatus.Shipped:
Console.WriteLine("発送済み");
break;
default:
throw new ArgumentOutOfRangeException(
nameof(value),
value,
"未定義の注文状態です。");
}
}
外部データを変換する場合は、次のいずれかで対策します。
Enum.IsDefinedで確認するswitchのdefaultで未定義値を処理するUnknownやNoneメンバーを定義する不正値として例外やエラーを返す
将来追加される値として安全に保持する
どの方法を選ぶかは、外部仕様やシステムの要件によって異なります。
5. C#のenumを文字列に変換する方法
5-1. ToStringメソッドでenumを文字列に変換する
enumのメンバー名を文字列として取得する最も簡単な方法は、ToStringメソッドです。
C#OrderStatus status = OrderStatus.Shipped;
string text = status.ToString();
Console.WriteLine(text); // Shipped
文字列補間でも、自動的に文字列へ変換されます。
C#Console.WriteLine($"現在の状態: {status}");
出力結果は次のようになります。
現在の状態: Shipped
5-2. enumの数値を文字列として取得する
enumの数値を文字列として取得する場合は、基になる整数型へキャストしてからToStringを呼び出します。
C#OrderStatus status = OrderStatus.Shipped;
string valueText = ((int)status).ToString();
Console.WriteLine(valueText); // 20
書式指定文字列のDを使用する方法もあります。
C#string valueText = status.ToString("D");
Console.WriteLine(valueText); // 20
enumでは主に次の書式指定を利用できます。
G:一般的な形式。定義済みなら名前、未定義なら数値D:10進数X:16進数F:フラグ形式
C#Console.WriteLine(status.ToString("G")); // Shipped
Console.WriteLine(status.ToString("D")); // 20
Console.WriteLine(status.ToString("X")); // 00000014
5-3. Enum.GetNameメソッドでメンバー名を取得する
Enum.GetNameメソッドを使うと、指定した値に対応するメンバー名を取得できます。
C#string? name = Enum.GetName(typeof(OrderStatus), OrderStatus.Shipped);
Console.WriteLine(name); // Shipped
数値を指定することもできます。
C#string? name = Enum.GetName(typeof(OrderStatus), 20);
Console.WriteLine(name); // Shipped
対応するメンバーが存在しない場合は、nullが返ります。
C#string? name = Enum.GetName(typeof(OrderStatus), 999);
Console.WriteLine(name is null); // True
ジェネリック版を使用できる環境では、次のように書けます。
C#string? name = Enum.GetName(OrderStatus.Shipped);
5-4. Enum.GetNamesメソッドでメンバー名の一覧を取得する
enumに定義されているすべてのメンバー名を取得するには、Enum.GetNamesメソッドを使います。
C#string[] names = Enum.GetNames(typeof(OrderStatus));
foreach (string name in names)
{
Console.WriteLine(name);
}
出力例は次のとおりです。
Pending
Paid
Shipped
Completed
Canceled
ジェネリック版を使用できる場合は、次のように記述できます。
C#string[] names = Enum.GetNames<OrderStatus>();
5-5. Enum.GetValuesメソッドで値の一覧を取得する
すべてのenum値を取得するには、Enum.GetValuesメソッドを使用します。
C#Array values = Enum.GetValues(typeof(OrderStatus));
foreach (OrderStatus status in values)
{
Console.WriteLine(status);
}
ジェネリック版を利用すると、型変換せずに配列を取得できます。
C#OrderStatus[] values = Enum.GetValues<OrderStatus>();
foreach (OrderStatus status in values)
{
Console.WriteLine(status);
}
メンバー名と数値を同時に表示する場合は、次のようにします。
C#foreach (OrderStatus status in Enum.GetValues<OrderStatus>())
{
Console.WriteLine($"{status}: {(int)status}");
}
6. 文字列をC#のenumに変換する方法
6-1. Enum.Parseメソッドで文字列をenumに変換する
文字列をenumへ変換するには、Enum.Parseメソッドを使用できます。
C#string text = "Shipped";
OrderStatus status =
(OrderStatus)Enum.Parse(typeof(OrderStatus), text);
Console.WriteLine(status); // Shipped
ジェネリック版を使うと、キャストが不要になります。
C#OrderStatus status = Enum.Parse<OrderStatus>("Shipped");
ただし、変換できない文字列を渡すとArgumentExceptionが発生します。
C#OrderStatus status = Enum.Parse<OrderStatus>("UnknownValue");
入力が必ず正しいと保証できる場合を除き、通常はEnum.TryParseの使用が安全です。
6-2. Enum.TryParseメソッドで安全に変換する
Enum.TryParseは、変換に成功したかどうかをboolで返します。
C#string text = "Shipped";
if (Enum.TryParse<OrderStatus>(text, out OrderStatus status))
{
Console.WriteLine($"変換成功: {status}");
}
else
{
Console.WriteLine("変換できませんでした。");
}
変換に失敗しても例外が発生しないため、ユーザー入力や外部データを扱う場合に適しています。
ただし、TryParseが成功しても、定義済みのメンバーとは限りません。数字の文字列を渡した場合、未定義の数値でも成功することがあります。
厳密に定義済みメンバーだけを受け付ける場合は、Enum.IsDefinedも組み合わせます。
C#string text = "20";
if (Enum.TryParse<OrderStatus>(text, out OrderStatus status) &&
Enum.IsDefined(status))
{
Console.WriteLine($"有効な値です: {status}");
}
else
{
Console.WriteLine("定義されていない値です。");
}
6-3. 大文字・小文字を区別せずに変換する
通常、Enum.ParseやEnum.TryParseは大文字と小文字を区別します。
C#Enum.TryParse<OrderStatus>("shipped", out OrderStatus status);
大文字・小文字を区別せずに変換する場合は、ignoreCaseにtrueを指定します。
C#bool success = Enum.TryParse(
"shipped",
ignoreCase: true,
out OrderStatus status);
Console.WriteLine(success); // True
Console.WriteLine(status); // Shipped
ジェネリック版では次のように記述できます。
C#if (Enum.TryParse<OrderStatus>(
"shipped",
ignoreCase: true,
out var status))
{
Console.WriteLine(status);
}
ユーザーが直接入力する値を扱う場合は、大文字・小文字を区別しないほうが使いやすいことがあります。一方、外部APIの仕様を厳密に検証したい場合は、区別する設計も考えられます。
6-4. 数字の文字列をenumに変換するときの挙動
Enum.ParseやEnum.TryParseは、メンバー名だけでなく数字の文字列も変換できます。
C#OrderStatus status = Enum.Parse<OrderStatus>("20");
Console.WriteLine(status); // Shipped
定義されていない数値でも、基になる整数型の範囲内であれば変換できます。
C#bool success = Enum.TryParse<OrderStatus>(
"999",
out OrderStatus status);
Console.WriteLine(success); // True
Console.WriteLine(status); // 999
そのため、TryParseの戻り値だけで「定義済みの値か」を判断してはいけません。
C#if (Enum.TryParse<OrderStatus>(text, out var status) &&
Enum.IsDefined(status))
{
// 定義済みのメンバー
}
数字の文字列を受け付けたくない場合は、先に数値として解釈できるか確認する方法もあります。
C#if (int.TryParse(text, out _))
{
Console.WriteLine("数値による指定は受け付けません。");
}
else if (Enum.TryParse<OrderStatus>(
text,
ignoreCase: true,
out var status))
{
Console.WriteLine(status);
}
6-5. 変換できない文字列を扱うときの注意点
空文字、スペルミス、不正な値などを扱う場合は、次の点に注意します。
C#string? text = GetInput();
if (string.IsNullOrWhiteSpace(text))
{
Console.WriteLine("値を入力してください。");
}
else if (!Enum.TryParse<OrderStatus>(
text,
ignoreCase: true,
out var status))
{
Console.WriteLine("有効な注文状態ではありません。");
}
else if (!Enum.IsDefined(status))
{
Console.WriteLine("定義されていない注文状態です。");
}
else
{
Console.WriteLine($"変換結果: {status}");
}
外部入力を扱う際は、次のようなルールを決めておくと安全です。
空文字を許可するか
大文字・小文字を区別するか
数字の文字列を許可するか
未定義値を許可するか
不正値をエラーにするか、既定値に置き換えるか
単純にParseするだけではなく、システムの入力仕様に合わせて検証することが大切です。
7. C#のenumを一覧表示・ループ処理する方法
7-1. Enum.GetValuesでenumをforeach処理する
enumのすべての値をループ処理するには、Enum.GetValuesを使用します。
C#foreach (OrderStatus status in Enum.GetValues(typeof(OrderStatus)))
{
Console.WriteLine(status);
}
この方法では、戻り値がArrayであるため、foreach内で型を指定します。
選択肢の生成、ログ出力、テストデータ作成などに利用できます。
7-2. Enum.GetNamesでメンバー名をループ処理する
メンバー名だけを処理したい場合は、Enum.GetNamesを使用します。
C#foreach (string name in Enum.GetNames(typeof(OrderStatus)))
{
Console.WriteLine(name);
}
取得できるのは文字列の配列です。
C#string[] names = Enum.GetNames(typeof(OrderStatus));
メンバー名をそのまま画面表示に使うこともできますが、英語の識別子と日本語の表示名は分離したほうが保守しやすい場合があります。
7-3. メンバー名と数値をセットで一覧表示する
名前と数値をセットで表示する場合は、enum値をループし、数値へキャストします。
C#foreach (OrderStatus status in Enum.GetValues(typeof(OrderStatus)))
{
Console.WriteLine($"{status} = {(int)status}");
}
LINQを使って、オブジェクトの一覧に変換することもできます。
C#var items = Enum.GetValues<OrderStatus>()
.Select(status => new
{
Value = (int)status,
Name = status.ToString()
})
.ToList();
foreach (var item in items)
{
Console.WriteLine($"{item.Value}: {item.Name}");
}
Web画面のセレクトボックスなどで利用する場合は、値と表示名を持つクラスへ変換すると扱いやすくなります。
C#public class SelectItem
{
public int Value { get; init; }
public string Text { get; init; } = string.Empty;
}
C#List<SelectItem> items = Enum.GetValues<OrderStatus>()
.Select(status => new SelectItem
{
Value = (int)status,
Text = status.ToString()
})
.ToList();
7-4. ジェネリック版Enum.GetValuesを使用する
ジェネリック版のEnum.GetValues<TEnum>を使うと、型安全な配列を取得できます。
C#OrderStatus[] statuses = Enum.GetValues<OrderStatus>();
そのままforeachで処理できます。
C#foreach (OrderStatus status in Enum.GetValues<OrderStatus>())
{
Console.WriteLine($"{status}: {(int)status}");
}
従来の書き方と比べて、キャストが不要で簡潔です。
C#// 従来の書き方
Array values = Enum.GetValues(typeof(OrderStatus));
// ジェネリック版
OrderStatus[] values = Enum.GetValues<OrderStatus>();
利用している.NETのバージョンでジェネリック版が使える場合は、基本的にこちらを選ぶとよいでしょう。
8. C#のFlags属性で複数のenum値を扱う方法
8-1. Flags属性とは
通常のenum変数は、1つのメンバーを表すために使います。一方、権限や機能の有効・無効など、複数の値を同時に保持したい場合があります。
このような用途では、Flags属性を付けたenumを使用します。
C#[Flags]
public enum Permission
{
None = 0,
Read = 1,
Write = 2,
Delete = 4
}
複数の権限を組み合わせると、次のように保持できます。
C#Permission permissions =
Permission.Read | Permission.Write;
8-2. ビットフラグ用のenumを定義する
Flags enumの各メンバーには、通常、2の累乗を割り当てます。
C#[Flags]
public enum Permission
{
None = 0,
Read = 1, // 0001
Write = 2, // 0010
Delete = 4, // 0100
ManageUsers = 8 // 1000
}
2進数リテラルを使うと、ビットの位置が分かりやすくなります。
C#[Flags]
public enum Permission
{
None = 0,
Read = 0b0001,
Write = 0b0010,
Delete = 0b0100,
ManageUsers = 0b1000
}
ビットシフト演算子を使う方法もあります。
C#[Flags]
public enum Permission
{
None = 0,
Read = 1 << 0,
Write = 1 << 1,
Delete = 1 << 2,
ManageUsers = 1 << 3
}
1、2、3、4のように連番を割り当てると、3がRead | Writeと重複してしまうため、フラグ用enumでは避ける必要があります。
8-3. 複数の値をビットOR演算子で組み合わせる
複数のフラグを組み合わせるには、ビットOR演算子|を使用します。
C#Permission permissions =
Permission.Read |
Permission.Write;
数値としては、1 | 2によって3になります。
C#Console.WriteLine((int)permissions); // 3
Flags属性が付いていると、ToStringの結果も読みやすくなります。
C#Console.WriteLine(permissions); // Read, Write
8-4. HasFlagメソッドで値を含むか判定する
指定したフラグを含んでいるかは、HasFlagメソッドで確認できます。
C#Permission permissions =
Permission.Read |
Permission.Write;
if (permissions.HasFlag(Permission.Read))
{
Console.WriteLine("読み取り権限があります。");
}
複数のフラグをまとめて指定すると、すべて含んでいる場合にtrueになります。
C#Permission required =
Permission.Read |
Permission.Write;
if (permissions.HasFlag(required))
{
Console.WriteLine("読み取り権限と書き込み権限があります。");
}
なお、Noneは0なので、permissions.HasFlag(Permission.None)は常にtrueになります。値がNoneかどうかを確認する場合は、等価比較を使用します。
C#if (permissions == Permission.None)
{
Console.WriteLine("権限がありません。");
}
8-5. ビットAND演算子で値を判定する
ビットAND演算子&でも、フラグを含んでいるか確認できます。
C#if ((permissions & Permission.Read) == Permission.Read)
{
Console.WriteLine("読み取り権限があります。");
}
複数のフラグをすべて含んでいるか確認する場合は、次のようにします。
C#Permission required =
Permission.Read |
Permission.Write;
if ((permissions & required) == required)
{
Console.WriteLine("必要な権限をすべて持っています。");
}
いずれか1つでも含んでいるか確認する場合は、Noneと比較します。
C#if ((permissions & required) != Permission.None)
{
Console.WriteLine("必要な権限の一部を持っています。");
}
8-6. フラグを追加・削除する方法
フラグを追加するには、OR演算子と複合代入演算子を使います。
C#Permission permissions = Permission.Read;
permissions |= Permission.Write;
Console.WriteLine(permissions); // Read, Write
フラグを削除するには、ビット反転演算子~とAND演算子を組み合わせます。
C#permissions &= ~Permission.Read;
Console.WriteLine(permissions); // Write
フラグの状態を反転するには、XOR演算子^を使用できます。
C#permissions ^= Permission.Write;
Writeが含まれていれば削除され、含まれていなければ追加されます。ただし、意図が分かりにくくなる場合があるため、業務コードでは明示的に追加・削除する方法も検討しましょう。
8-7. NoneとAllを定義するときのポイント
Flags enumには、値がない状態を表すNone = 0を定義するのが一般的です。
C#[Flags]
public enum Permission
{
None = 0,
Read = 1,
Write = 2,
Delete = 4
}
すべての値を表すAllを定義することもできます。
C#[Flags]
public enum Permission
{
None = 0,
Read = 1,
Write = 2,
Delete = 4,
All = Read | Write | Delete
}
All = -1と定義する方法も見られますが、将来追加される未定義ビットまで含んでしまう可能性があります。
C#All = -1
既知のフラグだけを表したい場合は、既存メンバーのORで定義するほうが意図が明確です。
新しいフラグを追加した際は、Allの更新を忘れないように注意します。
9. C#のenumに表示名や説明を設定する方法
9-1. Description属性で表示用の文字列を設定する
enumのメンバー名には、通常、英数字の識別子を使用します。日本語の表示名を設定したい場合は、Description属性を利用できます。
C#using System.ComponentModel;
public enum OrderStatus
{
[Description("支払い待ち")]
Pending = 0,
[Description("入金済み")]
Paid = 10,
[Description("発送済み")]
Shipped = 20,
[Description("取引完了")]
Completed = 30,
[Description("キャンセル済み")]
Canceled = 40
}
Description属性を付けただけでは、ToStringの結果は変わりません。
C#Console.WriteLine(OrderStatus.Shipped.ToString());
// Shipped
属性値を表示するには、リフレクションで取得する処理が必要です。
9-2. Display属性で表示名を設定する
ASP.NET Coreやデータアノテーションを使用する場面では、Display属性も利用できます。
C#using System.ComponentModel.DataAnnotations;
public enum OrderStatus
{
[Display(Name = "支払い待ち")]
Pending = 0,
[Display(Name = "入金済み")]
Paid = 10,
[Display(Name = "発送済み")]
Shipped = 20
}
Display属性には、表示名以外にも説明や表示順などを設定できます。
C#[Display(
Name = "発送済み",
Description = "商品の発送が完了した状態",
Order = 3)]
Shipped = 20
多言語対応では、リソースファイルと組み合わせることもできます。
C#[Display(
Name = "OrderStatus_Shipped",
ResourceType = typeof(Resources))]
Shipped = 20
9-3. リフレクションで属性値を取得する
Description属性の値を取得する例は次のとおりです。
C#using System.ComponentModel;
using System.Reflection;
public static string GetDescription(Enum value)
{
string name = value.ToString();
FieldInfo? field = value.GetType().GetField(name);
DescriptionAttribute? attribute =
field?.GetCustomAttribute<DescriptionAttribute>();
return attribute?.Description ?? name;
}
使用例は次のとおりです。
C#string text = GetDescription(OrderStatus.Shipped);
Console.WriteLine(text); // 発送済み
Display属性のNameを取得する場合は、次のようにします。
C#using System.ComponentModel.DataAnnotations;
using System.Reflection;
public static string GetDisplayName(Enum value)
{
string name = value.ToString();
FieldInfo? field = value.GetType().GetField(name);
DisplayAttribute? attribute =
field?.GetCustomAttribute<DisplayAttribute>();
return attribute?.GetName() ?? name;
}
未定義値の場合、GetFieldはnullになるため、メンバー名や数値をフォールバックとして返す処理が必要です。
9-4. enumと画面表示用文字列を分離する理由
enumのメンバー名をそのまま画面表示に使うと、次のような問題が起こります。
日本語表示にしにくい
文言変更がコード上の識別子に影響する
多言語対応が難しい
画面ごとに異なる表示をしにくい
APIやデータベースの値とUI文言が密結合になる
enumのメンバー名はプログラム内部の識別子、表示名はユーザー向けの文言として分離すると、設計が安定します。
C#OrderStatus.Shipped
内部ではShippedを使い、画面には「発送済み」と表示するイメージです。
ただし、属性にすべてのUI文言を持たせると、enumが特定の画面や言語に依存する場合があります。大規模なアプリケーションでは、リソースファイルや表示用モデルを利用する方法も検討しましょう。
9-5. 拡張メソッドで表示名の取得処理を共通化する
表示名の取得処理は、拡張メソッドにすると使いやすくなります。
C#using System.ComponentModel.DataAnnotations;
using System.Reflection;
public static class EnumExtensions
{
public static string GetDisplayName(this Enum value)
{
string name = value.ToString();
FieldInfo? field = value.GetType().GetField(name);
DisplayAttribute? attribute =
field?.GetCustomAttribute<DisplayAttribute>();
return attribute?.GetName() ?? name;
}
}
次のように呼び出せます。
C#string displayName =
OrderStatus.Shipped.GetDisplayName();
Console.WriteLine(displayName); // 発送済み
ジェネリックな拡張メソッドとして定義する方法もあります。
C#public static string GetDisplayName<TEnum>(this TEnum value)
where TEnum : struct, Enum
{
string name = value.ToString();
FieldInfo? field = typeof(TEnum).GetField(name);
DisplayAttribute? attribute =
field?.GetCustomAttribute<DisplayAttribute>();
return attribute?.GetName() ?? name;
}
表示名を繰り返し取得する場合、リフレクションの結果を辞書などにキャッシュすると、処理負荷を抑えられます。
10. C#のenumを実務で活用する具体例
10-1. ステータス管理にenumを使う
enumの代表的な用途は、業務データのステータス管理です。
C#public enum OrderStatus
{
Unknown = 0,
PendingPayment = 10,
Paid = 20,
PreparingShipment = 30,
Shipped = 40,
Completed = 50,
Canceled = 90
}
注文クラスのプロパティとして利用します。
C#public class Order
{
public int Id { get; set; }
public OrderStatus Status { get; private set; }
public void Ship()
{
if (Status != OrderStatus.Paid &&
Status != OrderStatus.PreparingShipment)
{
throw new InvalidOperationException(
"現在の状態では発送できません。");
}
Status = OrderStatus.Shipped;
}
}
enumを使うことで、状態遷移の条件が読みやすくなります。
ただし、状態ごとに複雑な振る舞いが増えてきた場合は、Stateパターンなど、クラスによる状態管理を検討したほうがよいこともあります。
10-2. 曜日や種別の管理にenumを使う
曜日には、.NET標準のDayOfWeek enumが用意されています。
C#DayOfWeek today = DateTime.Today.DayOfWeek;
if (today == DayOfWeek.Saturday ||
today == DayOfWeek.Sunday)
{
Console.WriteLine("休日です。");
}
独自の種別をenumで管理する例は次のとおりです。
C#public enum ProductType
{
Unknown = 0,
Physical = 1,
Digital = 2,
Subscription = 3
}
C#public decimal CalculateShippingFee(ProductType productType)
{
return productType switch
{
ProductType.Physical => 500m,
ProductType.Digital => 0m,
ProductType.Subscription => 0m,
_ => throw new ArgumentOutOfRangeException(
nameof(productType))
};
}
選択肢が固定されている分類であれば、enumによって処理の意図を明確にできます。
10-3. APIやJSONの値としてenumを扱う
System.Text.Jsonでenumをそのままシリアライズすると、既定では数値として出力されます。
C#using System.Text.Json;
var order = new
{
Id = 1,
Status = OrderStatus.Shipped
};
string json = JsonSerializer.Serialize(order);
出力例は次のようになります。
JSON{"Id":1,"Status":40}
文字列として出力する場合は、JsonStringEnumConverterを追加します。
C#using System.Text.Json;
using System.Text.Json.Serialization;
var options = new JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverter());
string json = JsonSerializer.Serialize(order, options);
出力例は次のとおりです。
JSON{"Id":1,"Status":"Shipped"}
ASP.NET Core全体に適用する場合は、JSONオプションにコンバーターを追加します。
C#builder.Services
.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(
new JsonStringEnumConverter());
});
文字列形式はJSONを人が読みやすくしますが、メンバー名の変更がAPI互換性に影響します。数値形式は名前変更に強い一方で、値の意味が分かりにくく、番号変更に弱いという特徴があります。
10-4. データベースにenumの値を保存する
データベースには、enumを数値または文字列として保存できます。
Entity Framework Coreでは、enumプロパティは通常、整数列として保存できます。
C#public class Order
{
public int Id { get; set; }
public OrderStatus Status { get; set; }
}
数値で保存する場合は、enumのメンバーに明示的な数値を設定することが重要です。
C#public enum OrderStatus
{
Unknown = 0,
Pending = 10,
Paid = 20,
Shipped = 30
}
文字列として保存する場合は、値変換を設定できます。
C#protected override void OnModelCreating(
ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.Property(order => order.Status)
.HasConversion<string>();
}
数値保存は容量が小さく、一般的に比較も効率的です。文字列保存はデータを直接見たときに意味が分かりやすい反面、メンバー名の変更に注意が必要です。
10-5. ASP.NET Coreのフォームや選択肢でenumを使う
ASP.NET Core MVCやRazor Pagesでは、enumから選択肢を生成できます。
モデルを次のように定義します。
C#using System.ComponentModel.DataAnnotations;
public enum OrderStatus
{
[Display(Name = "支払い待ち")]
Pending = 10,
[Display(Name = "入金済み")]
Paid = 20,
[Display(Name = "発送済み")]
Shipped = 30
}
public class OrderEditViewModel
{
public OrderStatus Status { get; set; }
}
Razorビューでは、Html.GetEnumSelectList<TEnum>()を使用できます。
HTML<select asp-for="Status"
asp-items="Html.GetEnumSelectList<OrderStatus>()">
</select>
nullable enumにすると、「未選択」の状態も表現できます。
C#[Required(ErrorMessage = "状態を選択してください。")]
public OrderStatus? Status { get; set; }
フォームから受け取った値は、モデル検証の結果を確認してから使用します。
C#if (!ModelState.IsValid)
{
return View(model);
}
11. C#のenumを使う際の注意点
11-1. enumの初期値が0になる仕組み
enum型のフィールドや配列要素などは、既定値として0になります。
C#OrderStatus status = default;
Console.WriteLine((int)status); // 0
クラスのプロパティも、明示的に値を設定しなければ0です。
C#public class Order
{
public OrderStatus Status { get; set; }
}
var order = new Order();
Console.WriteLine((int)order.Status); // 0
enumに0のメンバーがなくても、値としては0を保持できます。
C#public enum OrderStatus
{
Pending = 10,
Paid = 20
}
C#OrderStatus status = default;
Console.WriteLine(status); // 0
11-2. 0に対応するメンバーを定義する
既定値による不明な状態を避けるため、通常は0に対応するメンバーを定義します。
C#public enum OrderStatus
{
Unknown = 0,
Pending = 10,
Paid = 20,
Shipped = 30
}
フラグ用enumでは、None = 0を定義するのが一般的です。
C#[Flags]
public enum Permission
{
None = 0,
Read = 1,
Write = 2
}
ただし、Unknownを用意すると不正な状態が見逃される場合もあります。必ず値が必要な場面では、nullable enumやコンストラクターによる初期化、入力検証などを組み合わせます。
11-3. 同じ数値を複数のメンバーに割り当てる場合
C#では、複数のenumメンバーに同じ数値を割り当てられます。
C#public enum HttpResult
{
Success = 200,
Ok = 200,
NotFound = 404
}
これは別名や互換性維持のために使われることがあります。
ただし、数値から名前へ変換したとき、どちらの名前が返るかを前提にしたコードは避けるべきです。
C#HttpResult result = (HttpResult)200;
Console.WriteLine(result);
また、Enum.GetValuesでは同じ数値を持つメンバーもそれぞれ列挙されます。
重複値はコードを分かりにくくする可能性があるため、明確な理由がある場合だけ使用しましょう。
11-4. enumのメンバー順を変更するときのリスク
数値を指定せずにenumを定義すると、メンバーの順番によって値が決まります。
C#public enum OrderStatus
{
Pending, // 0
Paid, // 1
Shipped // 2
}
途中にメンバーを追加すると、後続メンバーの数値が変わります。
C#public enum OrderStatus
{
Pending, // 0
Confirmed, // 1
Paid, // 2
Shipped // 3
}
以前はPaidが1でしたが、追加後は2になります。データベースやファイルに数値を保存している場合、過去データの意味が変わってしまいます。
永続化や外部連携に使うenumでは、数値を明示します。
C#public enum OrderStatus
{
Pending = 0,
Paid = 10,
Shipped = 20,
Confirmed = 30
}
既存メンバーの番号は、公開後に変更しないのが原則です。
11-5. データベース保存時は数値と文字列のどちらを選ぶべきか
数値保存と文字列保存には、それぞれ長所と短所があります。
数値保存の主な特徴は次のとおりです。
保存サイズが小さい
比較やインデックスに向いている
メンバー名を変更しても値が変わらなければ影響しにくい
データベースを直接見ても意味が分かりにくい
番号変更によって過去データが壊れる
文字列保存の主な特徴は次のとおりです。
データベースを直接見ても意味が分かる
enumの数値順に依存しない
文字列の長さだけ保存容量が増える
メンバー名の変更が過去データとの不整合を生む
大文字・小文字や表記揺れに注意が必要
どちらを選ぶ場合でも、保存後の値を安易に変更しないことが重要です。外部仕様として長期間維持する値なら、enum名とは別の永続化用コードを用意する設計も考えられます。
11-6. 未定義値や将来追加される値に対応する
APIやメッセージ通信では、相手側のシステムに新しいenum値が追加されることがあります。
受信側が未知の値を必ず例外にすると、後方互換性が低下します。要件に応じて、次のような対応を検討します。
C#public enum ExternalStatus
{
Unknown = 0,
Active = 1,
Suspended = 2
}
C#ExternalStatus NormalizeStatus(int value)
{
return Enum.IsDefined(typeof(ExternalStatus), value)
? (ExternalStatus)value
: ExternalStatus.Unknown;
}
ただし、未知の値をUnknownに変換すると、元の数値を失います。将来値をそのまま保存する必要がある場合は、enumへの変換前の数値も保持する方法があります。
文字列値の場合は、未知の値を別プロパティへ保持するカスタムコンバーターや、文字列のまま扱う設計も選択肢です。
11-7. Enum.IsDefinedとFlags属性を併用するときの注意点
Flags enumの組み合わせ値に対してEnum.IsDefinedを使うと、期待と異なる結果になる場合があります。
C#[Flags]
public enum Permission
{
None = 0,
Read = 1,
Write = 2,
Delete = 4
}
C#Permission value =
Permission.Read |
Permission.Write;
bool defined = Enum.IsDefined(value);
Console.WriteLine(defined); // False
Read | Writeの数値は3ですが、3という単独メンバーは定義されていないためです。
Flags enumで有効なビットだけが使われているか確認するには、許可されたすべてのビットをまとめて判定します。
C#Permission all =
Permission.Read |
Permission.Write |
Permission.Delete;
bool isValid = (value & ~all) == 0;
または、Allメンバーを定義して使用します。
C#[Flags]
public enum Permission
{
None = 0,
Read = 1,
Write = 2,
Delete = 4,
All = Read | Write | Delete
}
C#bool isValid =
(value & ~Permission.All) == Permission.None;
12. C#のenumでよくあるエラーと解決方法
12-1. 文字列からenumへ変換できない
次のように、大文字・小文字やスペルが一致していないと変換に失敗します。
C#string text = "shipped";
bool success =
Enum.TryParse<OrderStatus>(text, out var status);
大文字・小文字を区別しない場合は、ignoreCase: trueを指定します。
C#bool success = Enum.TryParse<OrderStatus>(
text,
ignoreCase: true,
out var status);
前後に空白が含まれる可能性がある場合は、事前に取り除きます。
C#string normalizedText = text.Trim();
bool success = Enum.TryParse<OrderStatus>(
normalizedText,
ignoreCase: true,
out var status);
定義済みの値だけを許可する場合は、Enum.IsDefinedも確認します。
C#bool isValid =
Enum.TryParse<OrderStatus>(
normalizedText,
true,
out var status)
&& Enum.IsDefined(status);
12-2. 数値を変換すると未定義のenum値になる
数値をenumへキャストしても、値の存在確認は行われません。
C#OrderStatus status = (OrderStatus)999;
外部から取得した数値なら、キャスト前に確認します。
C#int value = 999;
if (!Enum.IsDefined(typeof(OrderStatus), value))
{
throw new ArgumentOutOfRangeException(
nameof(value),
value,
"未定義の注文状態です。");
}
OrderStatus status = (OrderStatus)value;
不正値を例外にせずUnknownへ変換する設計も可能です。
C#OrderStatus status =
Enum.IsDefined(typeof(OrderStatus), value)
? (OrderStatus)value
: OrderStatus.Unknown;
12-3. switch文ですべてのenum値を処理できていない
enumにメンバーを追加した後、既存のswitchに処理を追加し忘れることがあります。
C#string GetMessage(OrderStatus status)
{
return status switch
{
OrderStatus.Unknown => "不明",
OrderStatus.Pending => "支払い待ち",
OrderStatus.Paid => "入金済み",
OrderStatus.Shipped => "発送済み",
_ => throw new ArgumentOutOfRangeException(
nameof(status),
status,
"未対応の状態です。")
};
}
_で例外を投げておくと、未対応の値が入力されたことを検出できます。
ただし、将来の値を許容する必要があるシステムでは、例外ではなく「不明」として処理する場合もあります。
C#_ => "不明な状態"
どちらが適切かは、厳密性と互換性の要件によって決めます。
12-4. Flags属性で複数値の判定に失敗する
次の比較では、複数フラグを持つ値を正しく判定できません。
C#Permission permissions =
Permission.Read |
Permission.Write;
if (permissions == Permission.Read)
{
// 実行されない
}
permissionsはReadだけでなくWriteも含むため、完全一致にはなりません。
特定のフラグを含むか確認するには、HasFlagまたはビットANDを使います。
C#if (permissions.HasFlag(Permission.Read))
{
Console.WriteLine("読み取り可能");
}
C#if ((permissions & Permission.Read) ==
Permission.Read)
{
Console.WriteLine("読み取り可能");
}
また、フラグ値に1、2、3のような連番を割り当てていないか確認しましょう。各単独フラグには2の累乗を割り当てます。
12-5. JSONのenumが数値として出力される
System.Text.Jsonでは、既定でenumが数値として出力されます。
JSON{"status":30}
文字列として出力したい場合は、JsonStringEnumConverterを設定します。
C#var options = new JsonSerializerOptions
{
WriteIndented = true
};
options.Converters.Add(
new JsonStringEnumConverter());
string json =
JsonSerializer.Serialize(order, options);
特定のenum型だけに適用したい場合は、属性を付ける方法もあります。
C#using System.Text.Json.Serialization;
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum OrderStatus
{
Unknown = 0,
Pending = 10,
Paid = 20,
Shipped = 30
}
APIで形式を変更すると既存クライアントに影響するため、数値から文字列への変更は互換性を確認してから行います。
12-6. enumの表示名を日本語にできない
enumのメンバー名自体に日本語を使うことは技術的には可能ですが、一般的には英語の識別子を使います。
C#public enum OrderStatus
{
支払い待ち,
入金済み,
発送済み
}
この書き方は外部連携、チーム開発、命名規則などの面で扱いにくくなることがあります。
内部名は英語にし、Display属性やDescription属性で日本語を設定するのが一般的です。
C#public enum OrderStatus
{
[Display(Name = "支払い待ち")]
Pending = 10,
[Display(Name = "入金済み")]
Paid = 20,
[Display(Name = "発送済み")]
Shipped = 30
}
表示時は、属性値を取得する拡張メソッドを使用します。
13. C#のenumに関するよくある質問
13-1. enumのデフォルト値は何ですか
enumのデフォルト値は、基になる整数型の0です。
C#OrderStatus status = default;
Console.WriteLine((int)status); // 0
最初のメンバーが必ずデフォルト値になるわけではありません。最初のメンバーに別の数値を指定していても、デフォルト値は0です。
C#public enum OrderStatus
{
Pending = 10,
Paid = 20
}
この場合、default(OrderStatus)はどのメンバーにも対応しない0になります。そのため、通常はUnknown = 0やNone = 0を定義します。
13-2. enumに文字列を直接設定できますか
enumの基になる型は整数型に限られるため、メンバーに文字列を直接設定することはできません。
次のような定義はできません。
C#// コンパイルエラー
public enum OrderStatus
{
Pending = "PENDING"
}
文字列を関連付けたい場合は、次の方法があります。
Display属性を使うDescription属性を使う辞書で対応関係を管理する
拡張メソッドで文字列を返す
値と振る舞いを持つクラスを使う
たとえば、拡張メソッドで外部コードを返せます。
C#public static string ToExternalCode(
this OrderStatus status)
{
return status switch
{
OrderStatus.Pending => "PENDING",
OrderStatus.Paid => "PAID",
OrderStatus.Shipped => "SHIPPED",
_ => throw new ArgumentOutOfRangeException(
nameof(status))
};
}
13-3. enumは後からメンバーを追加できますか
ソースコードを変更して再ビルドすれば、メンバーを追加できます。
C#public enum OrderStatus
{
Unknown = 0,
Pending = 10,
Paid = 20,
Shipped = 30,
Returned = 40
}
ただし、追加時には次の影響を確認する必要があります。
switch文に新しい分岐が必要かデータベースの制約に影響しないか
APIのクライアントが新しい値を処理できるか
JSONのシリアライズ形式に影響しないか
UIの選択肢に自動表示されるか
Flags enumの
Allを更新する必要があるか
外部に公開したenumは、メンバー追加だけでも互換性問題を起こす可能性があります。
13-4. enumの値が定義済みか確認するにはどうすればよいですか
Enum.IsDefinedを使用します。
C#int value = 20;
bool isDefined =
Enum.IsDefined(typeof(OrderStatus), value);
enum値を直接確認することもできます。
C#OrderStatus status = (OrderStatus)value;
bool isDefined = Enum.IsDefined(status);
文字列の場合は、Enum.TryParseと組み合わせます。
C#bool isValid =
Enum.TryParse<OrderStatus>(
text,
ignoreCase: true,
out var status)
&& Enum.IsDefined(status);
Flags enumの組み合わせ値には、単純なEnum.IsDefinedではなく、有効ビット以外が含まれていないかを確認します。
13-5. enumとconstはどのように使い分けますか
関連する整数値の選択肢を1つの型として扱いたい場合は、enumが適しています。
C#public enum OrderStatus
{
Pending,
Paid,
Shipped
}
単独の固定値や、文字列・小数などを定義したい場合は、constが適しています。
C#public static class AppConstants
{
public const int MaxRetryCount = 3;
public const string DefaultCulture = "ja-JP";
}
enumは型安全な選択肢、constは変更されない個別の値と考えると分かりやすいでしょう。
13-6. enumとクラスはどのように使い分けますか
名前と数値だけで表せる固定的な選択肢なら、enumが適しています。
C#public enum PaymentMethod
{
Cash,
CreditCard,
BankTransfer
}
各種類に複数の属性や独自の処理が必要なら、クラスのほうが適している場合があります。
C#public abstract class PaymentMethod
{
public abstract string DisplayName { get; }
public abstract Task PayAsync(
decimal amount,
CancellationToken cancellationToken);
}
次のような要件が増えたら、クラスへの移行を検討します。
種類ごとに異なる振る舞いがある
複数の属性を保持する
実行時に種類を追加したい
継承やポリモーフィズムを利用したい
外部設定から種類を読み込みたい
enumに巨大なswitchが増え続ける場合は、クラス設計のほうが保守しやすい可能性があります。
13-7. enumをnullable型にするにはどうすればよいですか
型名の後ろに?を付けます。
C#OrderStatus? status = null;
値を代入することもできます。
C#status = OrderStatus.Paid;
値があるか確認するには、HasValueを使います。
C#if (status.HasValue)
{
Console.WriteLine(status.Value);
}
null合体演算子で既定値を設定できます。
C#OrderStatus actualStatus =
status ?? OrderStatus.Unknown;
パターンマッチングを使う方法もあります。
C#if (status is OrderStatus actualStatus)
{
Console.WriteLine(actualStatus);
}
フォームの未選択状態や、検索条件の指定なしを表す場合に便利です。
13-8. enumをJSONで文字列として扱うにはどうすればよいですか
System.Text.Jsonでは、JsonStringEnumConverterを使用します。
C#var options = new JsonSerializerOptions();
options.Converters.Add(
new JsonStringEnumConverter());
string json =
JsonSerializer.Serialize(
OrderStatus.Shipped,
options);
Console.WriteLine(json); // "Shipped"
逆に、文字列からenumへデシリアライズすることもできます。
C#OrderStatus status =
JsonSerializer.Deserialize<OrderStatus>(
"\"Shipped\"",
options);
Console.WriteLine(status); // Shipped
ASP.NET Core全体へ適用する場合は、次のように設定します。
C#builder.Services
.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(
new JsonStringEnumConverter());
});
APIでenumを文字列として扱う場合、メンバー名が外部仕様になります。内部リファクタリングだけのつもりで名前を変更すると、クライアントとの互換性が失われるため注意が必要です。
まとめ
C#のenumは、関連する複数の定数を1つの型として定義する仕組みです。注文状態、ユーザー種別、権限、処理結果など、あらかじめ選択肢が決まっている値を扱う場合に役立ちます。
基本的な定義方法は次のとおりです。
C#public enum OrderStatus
{
Unknown = 0,
Pending = 10,
Paid = 20,
Shipped = 30
}
enumを使うと、単なる数値や文字列よりもコードの意味が分かりやすくなり、入力補完や型チェックによってミスを減らせます。
数値との変換には明示的なキャスト、文字列との変換にはToString、Enum.Parse、Enum.TryParseなどを使用します。ただし、数値のキャストや数字の文字列からの変換では、未定義の値も受け入れられることがあります。外部入力を扱う場合は、必要に応じてEnum.IsDefinedによる確認を行いましょう。
複数の値を同時に保持する場合は、Flags属性とビット演算を使用します。フラグ用のメンバーには、1、2、4、8のような2の累乗を割り当てることが重要です。
また、enumのメンバー名と画面表示用の文字列は、Display属性やDescription属性によって分離できます。JSON、データベース、外部APIでenumを利用する場合は、数値やメンバー名が外部仕様になることを意識し、一度公開した値を安易に変更しないようにしましょう。
enumは便利ですが、実行時に種類が増えるデータや、種類ごとに複雑な属性・振る舞いを持つデータには向いていません。固定的な選択肢にはenum、動的なデータにはデータベース、複雑な振る舞いにはクラスというように、用途に合わせて使い分けることが大切です。

