C# enumとstringを相互変換する方法|文字列化・逆変換・Description属性まで徹底解説

はじめに

C#でenumを扱っていると、「enumをstringに変換したい」「stringからenumに戻したい」「画面には日本語で表示したい」「APIやJSONでは決まった文字列で返したい」といった場面がよくあります。

たとえば、内部処理では次のようにenumで状態を管理しているとします。

C#
public enum OrderStatus
{
Pending,
Paid,
Shipped,
Canceled
}

この値をログ、画面、JSON、APIレスポンス、ドロップダウンなどで扱う場合、OrderStatus.Pending"Pending""未払い"のような文字列に変換する必要があります。

この記事では、C#のenumstringを相互変換する基本から、Description属性、Display属性、EnumMember属性、JSON変換、実務向けユーティリティまでまとめて解説します。

1. C#のenumとstring変換でまず押さえるべき基本

1-1. enumとは?文字列変換が必要になる主な場面

enumは、関連する定数を名前付きで管理するための型です。数値をそのまま扱うよりも、意味のある名前で状態や種類を表現できるため、コードの可読性が高くなります。

C#
public enum UserRole
{
Admin,
Editor,
Viewer
}

たとえば012という数値だけでは意味が分かりにくいですが、UserRole.AdminUserRole.Viewerであれば役割が明確です。

一方で、次のような場面ではenumstringに変換する必要があります。

C#
UserRole role = UserRole.Admin;
string roleText = role.ToString(); // "Admin"

主な用途は、ログ出力、画面表示、CSV出力、JSONシリアライズ、APIレスポンス、設定ファイルへの保存、フォームやドロップダウンの表示などです。

また、外部から受け取った文字列を内部処理用のenumへ戻すケースもあります。

C#
string input = "Admin";
UserRole role = Enum.Parse<UserRole>(input);

このように、enumは内部処理に向いており、stringは外部入出力に向いています。両者を正しく変換できるようにしておくことが、実務では重要です。

1-2. enumをstringに変換する方法の全体像

enumstringに変換する方法はいくつかあります。

最も基本的なのはToString()です。

C#
UserRole role = UserRole.Admin;

string text = role.ToString(); // "Admin"

ToString()は、enumに定義されている名前を文字列として取得します。単純にenum名を表示したい場合は、これで十分です。

ただし、画面に表示する文字列として"Admin"ではなく"管理者"と出したい場合は、Description属性やDisplay属性を使う方法があります。

C#
public enum UserRole
{
[System.ComponentModel.Description("管理者")]
Admin,

[System.ComponentModel.Description("編集者")]
Editor,

[System.ComponentModel.Description("閲覧者")]
Viewer
}

また、APIやJSONで"admin""paid_user"のような文字列を使いたい場合は、EnumMember属性やJSONコンバーターを使う設計もあります。

つまり、enumからstringへの変換は、用途によって使い分けることが大切です。

1-3. stringをenumに戻す方法の全体像

stringenumに変換する代表的な方法は、Enum.ParseEnum.TryParseです。

C#
UserRole role = Enum.Parse<UserRole>("Admin");

ただし、Enum.Parseは変換できない文字列が渡されると例外を投げます。

C#
UserRole role = Enum.Parse<UserRole>("Unknown"); // 例外

そのため、ユーザー入力やAPIリクエストなど、外部から受け取る値を変換する場合はEnum.TryParseを使うのが安全です。

C#
if (Enum.TryParse<UserRole>("Admin", out var role))
{
Console.WriteLine(role);
}

さらに、Enum.TryParseは数値文字列も変換できてしまう点に注意が必要です。たとえば"999"のような文字列でも、型としてはUserRoleに変換できる場合があります。

そのため、実務ではEnum.IsDefinedと組み合わせて、定義済みのenum値かどうかを確認することが多いです。

1-4. ToString・Enum.Parse・Enum.TryParseの使い分け

基本的な使い分けは次のとおりです。

enumstringにしたい場合は、ToString()を使います。

C#
var text = UserRole.Admin.ToString(); // "Admin"

stringenumに変換し、失敗時に例外を発生させてもよい場合は、Enum.Parseを使います。

C#
var role = Enum.Parse<UserRole>("Admin");

変換できるか分からない文字列を扱う場合は、Enum.TryParseを使います。

C#
if (Enum.TryParse<UserRole>("Admin", out var role))
{
// 変換成功
}
else
{
// 変換失敗
}

実務では、外部入力に対してはEnum.TryParseを優先するのが基本です。Enum.Parseは、固定値やテストコードなど、値が必ず正しいと分かっている場面に限定すると安全です。

2. enumをstringに変換する方法

2-1. ToString()でenum名を文字列化する

enumを文字列に変換する最も簡単な方法は、ToString()です。

C#
public enum OrderStatus
{
Pending,
Paid,
Shipped,
Canceled
}

OrderStatus status = OrderStatus.Paid;

string text = status.ToString();

Console.WriteLine(text); // Paid

ToString()は、enumのメンバー名をそのまま返します。ログ出力やデバッグ用途であれば、まずこの方法を使えば問題ありません。

C#
Console.WriteLine($"注文ステータス: {status}");

文字列補間でも、内部的にはToString()が呼ばれるため、次のように出力されます。

C#
注文ステータス: Paid

ただし、ToString()で得られるのはあくまでC#コード上の名前です。画面表示用の日本語や、API仕様で決まった文字列とは別物として考える必要があります。

2-2. nameofとの違いと使い分け

nameofも名前を文字列として取得できますが、ToString()とは用途が異なります。

C#
string name = nameof(OrderStatus.Paid);

Console.WriteLine(name); // Paid

nameofはコンパイル時に名前を文字列化します。一方、ToString()は実行時のenum変数の値を文字列化します。

C#
OrderStatus status = OrderStatus.Shipped;

string text1 = status.ToString(); // "Shipped"
string text2 = nameof(OrderStatus.Paid); // "Paid"

変数に入っている値を文字列化したい場合はToString()を使います。

C#
string currentStatus = status.ToString();

特定のenumメンバー名を安全に文字列として書きたい場合はnameofが便利です。

C#
if (input == nameof(OrderStatus.Paid))
{
// Paidの場合の処理
}

nameofを使うと、リファクタリングでenum名を変更した場合にも追従しやすく、文字列のタイプミスを防げます。

2-3. 数値ではなく名前を取得したい場合の注意点

通常、定義済みのenum値に対してToString()を呼び出すと名前が返ります。

C#
OrderStatus status = OrderStatus.Paid;

Console.WriteLine(status.ToString()); // Paid

しかし、enumに定義されていない数値を代入した場合は、名前ではなく数値文字列が返ることがあります。

C#
OrderStatus status = (OrderStatus)999;

Console.WriteLine(status.ToString()); // 999

これは、999に対応するenumメンバー名が存在しないためです。

名前だけを取得したい場合は、Enum.GetNameを使う方法もあります。

C#
string? name = Enum.GetName(typeof(OrderStatus), status);

Console.WriteLine(name); // 定義済みでなければ null

また、定義済みの値かどうかを確認したい場合はEnum.IsDefinedを使います。

C#
if (Enum.IsDefined(typeof(OrderStatus), status))
{
Console.WriteLine(status.ToString());
}
else
{
Console.WriteLine("未定義のステータスです");
}

実務では、DBや外部APIから数値としてenumを受け取る場合に、未定義値が混入する可能性があります。表示前にバリデーションする設計にしておくと安全です。

2-4. Flags属性付きenumを文字列化する場合の挙動

複数の値を組み合わせたい場合は、Flags属性を付けたenumを使います。

C#
[Flags]
public enum Permission
{
None = 0,
Read = 1,
Write = 2,
Execute = 4
}

複数の権限を組み合わせると、ToString()ではカンマ区切りの文字列になることがあります。

C#
Permission permission = Permission.Read | Permission.Write;

Console.WriteLine(permission.ToString()); // Read, Write

Flags属性がある場合、ToString()は組み合わせ可能な値を名前として表現しようとします。

ただし、定義されていない組み合わせや、不正な値が含まれている場合は数値として表示されることがあります。

C#
Permission permission = (Permission)8;

Console.WriteLine(permission.ToString()); // 8

Flags付きenumでは、各値を2の累乗で定義することが重要です。

C#
[Flags]
public enum Permission
{
None = 0,
Read = 1,
Write = 2,
Execute = 4,
All = Read | Write | Execute
}

このように定義しておくと、文字列化やビット演算が自然に扱えます。

2-5. enumを小文字・大文字など任意の形式に整形する方法

ToString()で取得した文字列を、小文字や大文字に変換することもできます。

C#
OrderStatus status = OrderStatus.Paid;

string lower = status.ToString().ToLowerInvariant();
string upper = status.ToString().ToUpperInvariant();

Console.WriteLine(lower); // paid
Console.WriteLine(upper); // PAID

APIやJSONで小文字の値を使いたい場合は、このように変換できます。

C#
string apiValue = status.ToString().ToLowerInvariant();

ただし、ToLower()ではなくToLowerInvariant()を使うのが一般的です。カルチャによる影響を受けにくく、APIや内部処理の文字列変換に向いています。

また、PascalCaseのenum名をsnake_casekebab-caseに変換したい場合は、専用の変換処理を用意します。

C#
public enum PaymentStatus
{
WaitingForPayment,
PaymentCompleted
}

このようなenumを"waiting_for_payment"にしたい場合、単純なToLowerInvariant()だけでは"waitingforpayment"になってしまいます。

API仕様で文字列形式が決まっている場合は、EnumMember属性や専用のマッピングを使うほうが安全です。

3. stringをenumに変換する方法

3-1. Enum.Parseで文字列からenumに変換する

文字列からenumに変換する基本的な方法はEnum.Parseです。

C#
string text = "Paid";

OrderStatus status = Enum.Parse<OrderStatus>(text);

Console.WriteLine(status); // Paid

ジェネリック版を使うと、キャストが不要で読みやすくなります。

C#
OrderStatus status = Enum.Parse<OrderStatus>("Shipped");

古い書き方では、次のようにtypeofとキャストを使います。

C#
OrderStatus status = (OrderStatus)Enum.Parse(typeof(OrderStatus), "Shipped");

現在のC#では、基本的にジェネリック版を使うほうが分かりやすいです。

ただし、Enum.Parseは変換できない文字列が渡された場合に例外を投げます。

C#
OrderStatus status = Enum.Parse<OrderStatus>("Unknown"); // ArgumentException

そのため、外部入力に対して安易に使うと、予期しないエラーの原因になります。

3-2. Enum.TryParseで安全に変換する

変換に失敗する可能性がある場合は、Enum.TryParseを使います。

C#
string text = "Paid";

if (Enum.TryParse<OrderStatus>(text, out var status))
{
Console.WriteLine($"変換成功: {status}");
}
else
{
Console.WriteLine("変換失敗");
}

TryParseは、変換できた場合にtrueを返し、結果をout引数に格納します。変換できなかった場合はfalseを返すため、例外処理を書かずに安全に扱えます。

ユーザー入力、クエリ文字列、フォーム値、CSV、APIリクエストなど、外部から受け取る文字列は基本的に信用できません。そのため、実務ではEnum.ParseよりもEnum.TryParseを優先するのがおすすめです。

3-3. 大文字・小文字を区別せずに変換する方法

Enum.ParseEnum.TryParseでは、大文字・小文字を無視して変換できます。

C#
string text = "paid";

if (Enum.TryParse<OrderStatus>(text, ignoreCase: true, out var status))
{
Console.WriteLine(status); // Paid
}

ignoreCase: trueを指定すると、"Paid""paid""PAID"などを同じ値として扱えます。

Enum.Parseでも同様です。

C#
OrderStatus status = Enum.Parse<OrderStatus>("paid", ignoreCase: true);

APIや設定ファイルでは、大文字・小文字の揺れが起きることがあります。そのような場合はignoreCase: trueを指定すると扱いやすくなります。

ただし、変換ルールを緩くしすぎると、不正な値を見逃しやすくなる場合もあります。API仕様で大文字・小文字を厳密に決めている場合は、あえて区別する設計もあります。

3-4. 不正な文字列が渡された場合のエラー処理

Enum.Parseで不正な文字列を変換すると、ArgumentExceptionが発生します。

C#
try
{
OrderStatus status = Enum.Parse<OrderStatus>("Unknown");
}
catch (ArgumentException)
{
Console.WriteLine("不正なステータスです");
}

しかし、通常は例外を前提に制御するより、Enum.TryParseを使うほうがシンプルです。

C#
if (!Enum.TryParse<OrderStatus>("Unknown", out var status))
{
Console.WriteLine("不正なステータスです");
}

Web APIであれば、不正なenum文字列が渡された場合に400 Bad Requestを返すなど、入力値の検証として処理するのが一般的です。

C#
if (!Enum.TryParse<OrderStatus>(input, ignoreCase: true, out var status))
{
return Results.BadRequest("不正な注文ステータスです。");
}

重要なのは、変換失敗を想定した処理を必ず用意することです。

3-5. 変換結果が定義済みのenum値か確認する方法

Enum.TryParseで注意したいのは、数値文字列も変換できることです。

C#
Enum.TryParse<OrderStatus>("999", out var status);

Console.WriteLine(status); // 999

この場合、TryParse自体は成功することがあります。しかし、999OrderStatusに定義されていない値です。

定義済みのenum値だけを許可したい場合は、Enum.IsDefinedを組み合わせます。

C#
if (Enum.TryParse<OrderStatus>(input, ignoreCase: true, out var status)
&& Enum.IsDefined(typeof(OrderStatus), status))
{
Console.WriteLine($"有効な値: {status}");
}
else
{
Console.WriteLine("無効な値です");
}

ジェネリックな書き方にするなら、次のように書けます。

C#
if (Enum.TryParse<OrderStatus>(input, true, out var status)
&& Enum.IsDefined(status))
{
Console.WriteLine(status);
}

外部入力では、TryParseだけでなくIsDefinedまで確認することで、未定義のenum値を防ぎやすくなります。

4. Description属性を使ってenumに表示用文字列を持たせる

4-1. Description属性とは

Description属性は、enumメンバーに説明文や表示用文字列を付けるためによく使われる属性です。

C#
using System.ComponentModel;

public enum OrderStatus
{
[Description("注文受付")]
Pending,

[Description("支払い済み")]
Paid,

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

[Description("キャンセル")]
Canceled
}

ToString()では"Pending""Paid"のようなenum名しか取得できませんが、Description属性を使うと"注文受付""支払い済み"のような画面表示向けの文字列をenumに持たせられます。

画面表示、帳票、管理画面、ドロップダウンなどで日本語名を表示したい場合に便利です。

4-2. enumにDescription属性を付与する方法

Description属性を使うには、System.ComponentModel名前空間を追加します。

C#
using System.ComponentModel;

そして、各enumメンバーに属性を付与します。

C#
public enum PaymentMethod
{
[Description("クレジットカード")]
CreditCard,

[Description("銀行振込")]
BankTransfer,

[Description("代金引換")]
CashOnDelivery
}

この状態でToString()を呼んでも、取得できるのはenum名です。

C#
PaymentMethod method = PaymentMethod.CreditCard;

Console.WriteLine(method.ToString()); // CreditCard

Description属性の値を取得するには、リフレクションを使った処理が必要です。

4-3. Description属性の値を取得する拡張メソッド

Description属性の値を簡単に取得できるように、拡張メソッドを用意しておくと便利です。

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

public static class EnumExtensions
{
public static string GetDescription(this Enum value)
{
var type = value.GetType();
var name = value.ToString();

var field = type.GetField(name);
if (field == null)
{
return name;
}

var attribute = field.GetCustomAttribute<DescriptionAttribute>();

return attribute?.Description ?? name;
}
}

使用例は次のとおりです。

C#
PaymentMethod method = PaymentMethod.CreditCard;

string text = method.GetDescription();

Console.WriteLine(text); // クレジットカード

Description属性が設定されていない場合は、enum名を返すようにしておくと扱いやすくなります。

C#
return attribute?.Description ?? name;

このようなフォールバック処理を入れておくことで、属性の付け忘れがあってもエラーになりにくくなります。

4-4. Description文字列からenumへ逆変換する方法

Description属性の文字列からenumへ戻したい場合もあります。

たとえば、"クレジットカード"という文字列からPaymentMethod.CreditCardを取得したい場合です。

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

public static class EnumExtensions
{
public static bool TryParseFromDescription<TEnum>(
string description,
out TEnum result)
where TEnum : struct, Enum
{
foreach (var value in Enum.GetValues<TEnum>())
{
var enumValue = (Enum)(object)value;
var field = typeof(TEnum).GetField(enumValue.ToString());

var attribute = field?.GetCustomAttribute<DescriptionAttribute>();

if (attribute?.Description == description)
{
result = value;
return true;
}

if (enumValue.ToString() == description)
{
result = value;
return true;
}
}

result = default;
return false;
}
}

使用例は次のとおりです。

C#
if (EnumExtensions.TryParseFromDescription<PaymentMethod>(
"クレジットカード",
out var method))
{
Console.WriteLine(method); // CreditCard
}

Description属性の値と一致しなかった場合に、enum名との一致も確認しておくと、"CreditCard"のような文字列にも対応できます。

4-5. Description属性を使うメリット・デメリット

Description属性を使うメリットは、enumの定義と表示文字列を近い場所で管理できることです。

C#
public enum OrderStatus
{
[Description("注文受付")]
Pending,

[Description("支払い済み")]
Paid
}

画面表示用の簡単なラベルであれば、非常に分かりやすく実装できます。

一方で、デメリットもあります。Description属性の取得にはリフレクションが必要なため、大量に繰り返す場合はパフォーマンスに注意が必要です。また、多言語対応にはあまり向いていません。

さらに、Description属性はあくまで説明用の属性であり、APIやJSONの正式な値として使う場合は、用途が曖昧になることがあります。

画面表示にはDescriptionDisplay、APIやJSONにはEnumMemberや専用の変換ルール、といったように使い分けるのがおすすめです。

5. Display属性・EnumMember属性を使った文字列変換

5-1. Display属性で画面表示用の文字列を管理する方法

Display属性は、ASP.NET Core MVCやRazor Pages、Blazorなどで画面表示名を管理する際によく使われます。

C#
using System.ComponentModel.DataAnnotations;

public enum UserRole
{
[Display(Name = "管理者")]
Admin,

[Display(Name = "編集者")]
Editor,

[Display(Name = "閲覧者")]
Viewer
}

Display属性のNameを取得する拡張メソッドは、次のように書けます。

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

public static class EnumDisplayExtensions
{
public static string GetDisplayName(this Enum value)
{
var field = value.GetType().GetField(value.ToString());

var attribute = field?.GetCustomAttribute<DisplayAttribute>();

return attribute?.GetName() ?? value.ToString();
}
}

使用例です。

C#
UserRole role = UserRole.Admin;

Console.WriteLine(role.GetDisplayName()); // 管理者

DisplayAttributeにはName以外にもShortNameDescriptionなどを設定できます。画面表示やフォーム項目と相性がよい属性です。

5-2. EnumMember属性でAPI・JSON用の文字列を定義する方法

EnumMember属性は、シリアライズ時の値を定義したい場合によく使われます。

C#
using System.Runtime.Serialization;

public enum PaymentStatus
{
[EnumMember(Value = "waiting_for_payment")]
WaitingForPayment,

[EnumMember(Value = "payment_completed")]
PaymentCompleted,

[EnumMember(Value = "payment_failed")]
PaymentFailed
}

APIやJSONでは、C#のenum名であるWaitingForPaymentよりも、"waiting_for_payment"のような値を使いたいことがあります。

このような場合、EnumMember属性で外部向けの文字列を明示しておくと、C#コード上の名前とAPI仕様上の値を分離できます。

ただし、System.Text.Jsonの標準的なJsonStringEnumConverterは、EnumMember属性をそのまま自動反映しない点に注意が必要です。EnumMemberをJSONに反映したい場合は、カスタムコンバーターや対応ライブラリ、またはNewtonsoft.JsonStringEnumConverterを検討します。

5-3. Description・Display・EnumMemberの違い

DescriptionDisplayEnumMemberは似ていますが、用途が異なります。

Description属性は、簡単な説明文や表示用ラベルを付けたい場合に使いやすい属性です。小規模な画面表示やログ表示に向いています。

Display属性は、UI表示を意識した属性です。ASP.NET Core MVCのフォーム、バリデーション、表示名などと相性がよく、画面に出す文言を管理したい場合に適しています。

EnumMember属性は、シリアライズや外部連携を意識した属性です。API、JSON、外部仕様で決められた文字列をenumに対応させたい場合に向いています。

たとえば、同じenumでも次のように役割を分けられます。

C#
public enum PaymentStatus
{
[Display(Name = "支払い待ち")]
[EnumMember(Value = "waiting_for_payment")]
WaitingForPayment
}

画面では"支払い待ち"を表示し、APIでは"waiting_for_payment"を使う、という設計です。

5-4. 用途別にどの属性を使うべきか

画面表示用の日本語ラベルを管理したい場合は、Display属性がおすすめです。ASP.NET Core系のUIと相性がよく、表示名としての意図が明確だからです。

C#
[Display(Name = "管理者")]
Admin

簡単な説明文字列を付けたいだけなら、Description属性でも十分です。

C#
[Description("管理者")]
Admin

APIやJSONで外部仕様に合わせた文字列を使いたい場合は、EnumMember属性や専用の変換ルールを使います。

C#
[EnumMember(Value = "admin")]
Admin

重要なのは、画面表示用の文字列とAPI用の文字列を混同しないことです。画面文言は変更される可能性がありますが、APIの値は一度公開すると簡単には変更できません。

そのため、実務では次のような使い分けが安全です。

C#
public enum UserRole
{
[Display(Name = "管理者")]
[EnumMember(Value = "admin")]
Admin,

[Display(Name = "編集者")]
[EnumMember(Value = "editor")]
Editor
}

5-5. 属性値が未設定の場合のフォールバック処理

属性を使う場合でも、すべてのenumメンバーに必ず属性が設定されているとは限りません。

そのため、属性値がない場合はenum名を返すフォールバック処理を入れておくと安全です。

C#
public static string GetDisplayName(this Enum value)
{
var field = value.GetType().GetField(value.ToString());

var attribute = field?.GetCustomAttribute<DisplayAttribute>();

return attribute?.GetName() ?? value.ToString();
}

Description属性でも同様です。

C#
return attribute?.Description ?? value.ToString();

フォールバック処理がないと、属性の付け忘れによってnullが返ったり、画面表示で例外が発生したりする可能性があります。

特に、enumは後からメンバーが追加されることが多いです。将来の追加に備えて、属性が未設定でも最低限動作する設計にしておきましょう。

6. JSON・APIでenumをstringとして扱う方法

6-1. System.Text.Jsonでenumを文字列としてシリアライズする

System.Text.Jsonでは、標準設定のままだとenumは数値としてシリアライズされることがあります。

C#
public enum OrderStatus
{
Pending,
Paid,
Shipped
}

public class Order
{
public OrderStatus Status { get; set; }
}

標準設定では、次のように出力される場合があります。

JSON
{
"status": 1
}

APIレスポンスで数値の1だけが返ると、クライアント側で意味が分かりにくくなります。文字列として返したい場合は、JsonStringEnumConverterを使います。

6-2. JsonStringEnumConverterの使い方

JsonStringEnumConverterを使うと、enumを文字列としてJSONに変換できます。

C#
using System.Text.Json;
using System.Text.Json.Serialization;

var options = new JsonSerializerOptions
{
Converters =
{
new JsonStringEnumConverter()
}
};

var order = new Order
{
Status = OrderStatus.Paid
};

string json = JsonSerializer.Serialize(order, options);

Console.WriteLine(json);

出力例です。

JSON
{
"Status": "Paid"
}

ASP.NET Coreで全体設定する場合は、Program.csで次のように設定します。

C#
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});

MVCコントローラーを使っている場合は、次のように設定します。

C#
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(
new JsonStringEnumConverter());
});

小文字で出力したい場合は、命名ポリシーを指定します。

C#
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)

これにより、PaymentCompleted"paymentCompleted"のように出力されます。

6-3. Newtonsoft.Jsonでenumをstring化する方法

Newtonsoft.Jsonを使っている場合は、StringEnumConverterを使います。

C#
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

public class Order
{
[JsonConverter(typeof(StringEnumConverter))]
public OrderStatus Status { get; set; }
}

シリアライズすると、enumが文字列として出力されます。

JSON
{
"Status": "Paid"
}

全体に適用したい場合は、設定にコンバーターを追加します。

C#
var settings = new JsonSerializerSettings();
settings.Converters.Add(new StringEnumConverter());

string json = JsonConvert.SerializeObject(order, settings);

Newtonsoft.JsonStringEnumConverterは、EnumMember属性のValueを反映したい場合にもよく使われます。

C#
using System.Runtime.Serialization;

public enum PaymentStatus
{
[EnumMember(Value = "waiting_for_payment")]
WaitingForPayment,

[EnumMember(Value = "payment_completed")]
PaymentCompleted
}

このような定義により、API仕様に合わせた文字列を出力しやすくなります。

6-4. APIレスポンスでenum名を返す場合の注意点

APIレスポンスでenum名をそのまま返す場合は、C#コード上の名前が外部仕様になります。

JSON
{
"status": "PaymentCompleted"
}

一見便利ですが、将来C#側でenum名を変更すると、APIレスポンスの値も変わってしまうリスクがあります。

たとえば、PaymentCompletedPaidにリネームすると、クライアントが期待している"PaymentCompleted"が返らなくなる可能性があります。

APIでは、一度公開した文字列は契約として扱われます。そのため、外部に公開する値はC#のenum名に依存させすぎないほうが安全です。

C#
[EnumMember(Value = "payment_completed")]
PaymentCompleted

このように外部用の値を明示しておくと、C#の内部名を変更してもAPI仕様を維持しやすくなります。

6-5. EnumMember属性をJSON変換に反映する方法

EnumMember属性をJSON変換に反映したい場合は、使用しているJSONライブラリによって対応方法が異なります。

Newtonsoft.Jsonでは、StringEnumConverterを使うことでEnumMember属性の値を扱いやすくなります。

C#
[JsonConverter(typeof(StringEnumConverter))]
public PaymentStatus Status { get; set; }

一方、System.Text.Jsonの標準的なJsonStringEnumConverterは、EnumMember属性をそのまま自動反映する用途には向いていません。その場合は、カスタムコンバーターを作成するか、EnumMember対応のコンバーターを導入する必要があります。

簡単なAPIであれば、DTO側で文字列プロパティを用意して明示的に変換する方法もあります。

C#
public class PaymentResponse
{
public string Status { get; set; } = "";
}
C#
var response = new PaymentResponse
{
Status = "payment_completed"
};

規模が大きい場合は、enumと文字列の変換ルールを共通化し、API全体で一貫した変換が行われるように設計しましょう。

7. enumとstring変換でよくあるエラーと対処法

7-1. ArgumentExceptionが発生する原因

Enum.Parseで存在しない文字列を変換しようとすると、ArgumentExceptionが発生します。

C#
var status = Enum.Parse<OrderStatus>("Unknown");

OrderStatusUnknownというメンバーが定義されていなければ、変換できません。

対処法としては、Enum.TryParseを使います。

C#
if (!Enum.TryParse<OrderStatus>("Unknown", out var status))
{
Console.WriteLine("変換できませんでした");
}

外部入力を処理する場合は、例外が発生するParseよりも、失敗を判定できるTryParseを使うほうが安全です。

7-2. nullや空文字を変換するときの注意点

nullや空文字をEnum.Parseに渡すと、例外の原因になります。

C#
string? input = null;

var status = Enum.Parse<OrderStatus>(input!); // 例外

変換前に、string.IsNullOrWhiteSpaceでチェックしましょう。

C#
if (string.IsNullOrWhiteSpace(input))
{
Console.WriteLine("入力が空です");
}
else if (Enum.TryParse<OrderStatus>(input, out var status))
{
Console.WriteLine(status);
}

空文字、空白、nullは、通常の不正値とは別に扱ったほうがエラーメッセージを分けやすくなります。

C#
if (string.IsNullOrWhiteSpace(input))
{
return "ステータスは必須です";
}

if (!Enum.TryParse<OrderStatus>(input, true, out var status))
{
return "ステータスの値が不正です";
}

7-3. 数値文字列がenumに変換されてしまう問題

Enum.TryParseは、enum名だけでなく数値文字列も変換できます。

C#
Enum.TryParse<OrderStatus>("1", out var status);

Console.WriteLine(status); // Paid

さらに、定義されていない数値でも変換できることがあります。

C#
Enum.TryParse<OrderStatus>("999", out var status);

Console.WriteLine(status); // 999

これは、外部入力のバリデーションでは問題になることがあります。enum名だけを許可したい場合は、数値文字列を事前に拒否する方法があります。

C#
if (int.TryParse(input, out _))
{
Console.WriteLine("数値文字列は許可しません");
}
else if (Enum.TryParse<OrderStatus>(input, true, out var status)
&& Enum.IsDefined(status))
{
Console.WriteLine(status);
}

APIで"Paid"のような文字列だけを許可したい場合は、数値入力を明示的に拒否する設計にしましょう。

7-4. 未定義のenum値を防ぐバリデーション

未定義のenum値を防ぐには、Enum.IsDefinedを使います。

C#
public static bool IsValidEnum<TEnum>(TEnum value)
where TEnum : struct, Enum
{
return Enum.IsDefined(value);
}

使用例です。

C#
OrderStatus status = (OrderStatus)999;

if (!IsValidEnum(status))
{
Console.WriteLine("未定義のenum値です");
}

文字列から変換する場合は、TryParseIsDefinedを組み合わせます。

C#
public static bool TryParseDefined<TEnum>(
string input,
out TEnum result)
where TEnum : struct, Enum
{
if (Enum.TryParse<TEnum>(input, true, out result)
&& Enum.IsDefined(result))
{
return true;
}

result = default;
return false;
}

このようなメソッドを用意しておくと、アプリケーション全体で安全なenum変換を統一できます。

7-5. 日本語文字列をenumに変換できない場合の対処法

Enum.ParseEnum.TryParseは、基本的にenumメンバー名をもとに変換します。

C#
public enum OrderStatus
{
Pending,
Paid,
Shipped
}

この場合、"Paid"は変換できますが、"支払い済み"は変換できません。

C#
Enum.TryParse<OrderStatus>("支払い済み", out var status); // false

日本語文字列からenumに変換したい場合は、Description属性やDisplay属性の値を検索する処理が必要です。

C#
public enum OrderStatus
{
[Description("注文受付")]
Pending,

[Description("支払い済み")]
Paid,

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

このような属性を使い、属性値からenumを探すメソッドを用意します。

C#
if (EnumExtensions.TryParseFromDescription<OrderStatus>(
"支払い済み",
out var status))
{
Console.WriteLine(status); // Paid
}

日本語表示名は変更される可能性があるため、保存値やAPI値として使う場合は注意が必要です。DBやAPIでは安定したコード値を使い、画面表示だけ日本語に変換する設計がおすすめです。

8. 実務で使いやすいenum変換ユーティリティの作り方

8-1. enumから文字列を取得する汎用メソッド

実務では、enumから文字列を取得する処理を共通化しておくと便利です。

C#
public static class EnumUtil
{
public static string ToName<TEnum>(TEnum value)
where TEnum : struct, Enum
{
return value.ToString();
}
}

使用例です。

C#
string name = EnumUtil.ToName(OrderStatus.Paid);

Console.WriteLine(name); // Paid

この程度であればToString()を直接使ってもよいですが、将来的にDescriptionEnumMemberに対応したい場合は、共通メソッドを用意しておくと変更しやすくなります。

8-2. 文字列からenumを取得する汎用メソッド

文字列からenumに変換する処理も、共通化しておくと安全です。

C#
public static TEnum ParseEnum<TEnum>(string value)
where TEnum : struct, Enum
{
return Enum.Parse<TEnum>(value, ignoreCase: true);
}

ただし、このメソッドは変換に失敗すると例外を投げます。外部入力用には、次のようなTry形式のメソッドのほうが実用的です。

C#
public static bool TryParseEnum<TEnum>(
string? value,
out TEnum result)
where TEnum : struct, Enum
{
if (string.IsNullOrWhiteSpace(value))
{
result = default;
return false;
}

return Enum.TryParse<TEnum>(value, ignoreCase: true, out result)
&& Enum.IsDefined(result);
}

使用例です。

C#
if (EnumUtil.TryParseEnum<OrderStatus>("Paid", out var status))
{
Console.WriteLine(status);
}

8-3. Description属性に対応した拡張メソッド

Description属性を扱う場合は、拡張メソッドにしておくと自然に使えます。

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

public static class EnumDescriptionExtensions
{
public static string ToDescription(this Enum value)
{
var field = value.GetType().GetField(value.ToString());

var attribute = field?.GetCustomAttribute<DescriptionAttribute>();

return attribute?.Description ?? value.ToString();
}
}

使用例です。

C#
OrderStatus status = OrderStatus.Paid;

string text = status.ToDescription();

Console.WriteLine(text); // 支払い済み

拡張メソッドにすることで、status.ToDescription()のように直感的に呼び出せます。

8-4. Try形式で例外を出さない変換メソッド

属性値からenumに戻す場合も、例外を出さないTry形式にしておくと扱いやすいです。

C#
public static bool TryFromDescription<TEnum>(
string? text,
out TEnum result)
where TEnum : struct, Enum
{
result = default;

if (string.IsNullOrWhiteSpace(text))
{
return false;
}

foreach (var value in Enum.GetValues<TEnum>())
{
var enumValue = (Enum)(object)value;
var field = typeof(TEnum).GetField(enumValue.ToString());

var attribute = field?.GetCustomAttribute<DescriptionAttribute>();

if (attribute?.Description == text || enumValue.ToString() == text)
{
result = value;
return true;
}
}

return false;
}

使用例です。

C#
if (EnumUtil.TryFromDescription<OrderStatus>("支払い済み", out var status))
{
Console.WriteLine(status); // Paid
}

画面から送信された表示文字列をenumへ戻す場合に使えます。ただし、可能であればフォームの値には表示名ではなくenum名やコード値を使うほうが安全です。

8-5. キャッシュを使ってリフレクションの負荷を減らす方法

Description属性やDisplay属性の取得にはリフレクションを使います。通常の画面表示程度であれば大きな問題になることは少ないですが、大量データを処理する場合はキャッシュを使うと効率的です。

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

public static class EnumDescriptionCache
{
private static readonly ConcurrentDictionary<Enum, string> Cache = new();

public static string GetDescriptionCached(this Enum value)
{
return Cache.GetOrAdd(value, v =>
{
var field = v.GetType().GetField(v.ToString());

var attribute = field?.GetCustomAttribute<DescriptionAttribute>();

return attribute?.Description ?? v.ToString();
});
}
}

使用例です。

C#
string text = OrderStatus.Paid.GetDescriptionCached();

同じenum値に対して何度も属性取得を行う場合、キャッシュによりリフレクションの回数を減らせます。

より本格的に作るなら、enum型ごとに辞書を作り、enum値 → 表示文字列表示文字列 → enum値の双方向マップを保持する設計にすると便利です。

9. enumとstring変換のベストプラクティス

9-1. 内部処理ではenum、外部入出力ではstringを使い分ける

内部処理では、文字列よりもenumを使うほうが安全です。

C#
if (status == OrderStatus.Paid)
{
// 支払い済みの場合の処理
}

文字列で比較すると、タイプミスや表記揺れのリスクがあります。

C#
if (statusText == "Paid")
{
// タイプミスに気づきにくい
}

一方で、外部入出力では文字列が必要になることが多いです。API、JSON、CSV、画面表示、ログなどでは、enumを適切な文字列へ変換します。

つまり、アプリケーション内部ではenumで型安全に扱い、外部との境界でstringに変換する設計が基本です。

9-2. 画面表示用文字列をenum名に依存させない

ToString()で取得できるenum名を、そのまま画面表示に使うのは簡単です。

C#
Console.WriteLine(status.ToString()); // Paid

しかし、実際の画面では"Paid"よりも"支払い済み"のような表示が求められることが多いです。

また、enum名はコード都合で変更される可能性があります。画面表示文言とenum名が密結合していると、リファクタリングがしにくくなります。

画面表示用の文字列は、Display属性やDescription属性、リソースファイルなどで管理するのがおすすめです。

C#
[Display(Name = "支払い済み")]
Paid

多言語対応が必要な場合は、属性に直接日本語を書くより、リソースファイルで管理する設計を検討しましょう。

9-3. API仕様では変換ルールを明確にする

APIでenumをstringとして扱う場合は、どの文字列を受け付け、どの文字列を返すのかを明確にする必要があります。

たとえば、次のような仕様です。

JSON
{
"status": "payment_completed"
}

この値が"PaymentCompleted"なのか、"paymentCompleted"なのか、"payment_completed"なのかを曖昧にしてはいけません。

APIでは、C#のenum名をそのまま公開するより、外部仕様として安定した文字列を定義するほうが安全です。

C#
[EnumMember(Value = "payment_completed")]
PaymentCompleted

APIドキュメントにも、使用可能な値を明記しましょう。

status:
- waiting_for_payment
- payment_completed
- payment_failed

変換ルールを明確にしておくことで、フロントエンドや外部システムとの認識違いを防げます。

9-4. 例外が起きるParseよりTryParseを優先する

外部入力を変換する場合は、Enum.ParseよりEnum.TryParseを優先しましょう。

C#
if (Enum.TryParse<OrderStatus>(input, true, out var status))
{
// 成功
}
else
{
// 失敗
}

Enum.Parseは、変換できない値が渡されると例外を投げます。例外処理はコストが高く、通常の入力チェックとして使うには向いていません。

特に、Web APIやフォーム入力では、不正な文字列が渡されることを前提に設計する必要があります。

C#
if (!Enum.TryParse<OrderStatus>(input, true, out var status)
|| !Enum.IsDefined(status))
{
return Results.BadRequest("不正なステータスです。");
}

TryParseIsDefinedを組み合わせることで、より安全な変換ができます。

9-5. 将来のenum追加に強い設計にする

enumは、サービスの成長に伴って値が追加されることがあります。

C#
public enum OrderStatus
{
Pending,
Paid,
Shipped,
Canceled,
Refunded
}

新しい値を追加したときに、表示文字列、JSON変換、DB保存、API仕様、画面のドロップダウンなどが正しく対応できる設計にしておくことが重要です。

たとえば、属性が未設定の場合はenum名を返すフォールバックを用意します。

C#
return attribute?.GetName() ?? value.ToString();

また、switch式を使う場合は、未対応値に対する処理も考えておきます。

C#
string label = status switch
{
OrderStatus.Pending => "注文受付",
OrderStatus.Paid => "支払い済み",
OrderStatus.Shipped => "発送済み",
OrderStatus.Canceled => "キャンセル",
_ => "不明"
};

将来の追加を考えるなら、enum変換処理をアプリケーション内に散らばらせず、共通メソッドや変換クラスに集約するのがおすすめです。

10. C# enumとstring変換に関するよくある質問

10-1. enumの値ではなく日本語名を表示するには?

日本語名を表示したい場合は、Description属性やDisplay属性を使います。

C#
public enum OrderStatus
{
[Description("支払い済み")]
Paid
}

取得用の拡張メソッドを用意します。

C#
string text = OrderStatus.Paid.ToDescription();

画面表示が主な目的であれば、Display属性もおすすめです。

C#
[Display(Name = "支払い済み")]
Paid

ToString()"Paid"のようなenum名を返すだけなので、日本語表示には向いていません。

10-2. stringからenumに変換できるか判定するには?

Enum.TryParseを使います。

C#
bool canConvert = Enum.TryParse<OrderStatus>(
"Paid",
ignoreCase: true,
out var status);

定義済みの値だけを許可したい場合は、Enum.IsDefinedも組み合わせます。

C#
bool isValid =
Enum.TryParse<OrderStatus>("Paid", true, out var status)
&& Enum.IsDefined(status);

外部入力では、この形でチェックするのが安全です。

10-3. enumの数値をstringに変換するには?

enumの数値を文字列として取得したい場合は、数値型にキャストしてからToString()します。

C#
OrderStatus status = OrderStatus.Paid;

string numberText = ((int)status).ToString();

Console.WriteLine(numberText); // 1

また、enumのToString("D")を使う方法もあります。

C#
string numberText = status.ToString("D");

enum名ではなく数値を保存したい場合に使えます。ただし、DBやAPIで数値を使うと、値の意味が分かりにくくなるため注意が必要です。

10-4. enumをSelectListやドロップダウンに表示するには?

ASP.NET Core MVCなどでドロップダウンに表示する場合は、enum値と表示名のペアを作ります。

C#
var items = Enum.GetValues<OrderStatus>()
.Select(x => new
{
Value = x.ToString(),
Text = ((Enum)(object)x).GetDisplayName()
})
.ToList();

Valueには送信値として使うenum名を、Textには画面表示用の日本語名を設定します。

C#
[Display(Name = "支払い済み")]
Paid

このようにしておくと、画面には"支払い済み"を表示し、送信値としては"Paid"を扱えます。

10-5. Description属性とToStringはどちらを使うべき?

用途によって使い分けます。

ログやデバッグでenum名をそのまま出したい場合は、ToString()で十分です。

C#
status.ToString(); // Paid

画面に日本語やユーザー向けの文言を表示したい場合は、Description属性やDisplay属性を使います。

C#
[Description("支払い済み")]
Paid

ToString()はコード上の名前、DescriptionDisplayは表示用の文字列、と考えると分かりやすいです。

10-6. enumの文字列変換はパフォーマンスに影響する?

ToString()Enum.TryParseを通常の画面処理で使う程度であれば、大きな問題になることは少ないです。

ただし、Description属性やDisplay属性を取得する処理ではリフレクションを使うため、大量データに対して繰り返し実行する場合は注意が必要です。

C#
var attribute = field?.GetCustomAttribute<DescriptionAttribute>();

一覧画面で数千件、バッチ処理で数百万件のように大量に変換する場合は、キャッシュを使うと効率的です。

C#
private static readonly ConcurrentDictionary<Enum, string> Cache = new();

実務では、まず分かりやすい実装で始め、性能が問題になる箇所だけキャッシュ化するのが現実的です。

まとめ

C#でenumstringを相互変換する基本は、ToString()Enum.ParseEnum.TryParseです。

enumを文字列化するだけなら、次のように書けます。

C#
string text = OrderStatus.Paid.ToString();

文字列からenumに戻す場合は、外部入力を考慮してEnum.TryParseを使うのがおすすめです。

C#
if (Enum.TryParse<OrderStatus>(input, true, out var status)
&& Enum.IsDefined(status))
{
// 有効なenum値
}

画面に日本語名を表示したい場合は、Description属性やDisplay属性を使います。

C#
[Display(Name = "支払い済み")]
Paid

APIやJSONで外部仕様に合わせた文字列を扱う場合は、EnumMember属性やJSONコンバーターを使い、C#のenum名と外部公開値を分離する設計が安全です。

C#
[EnumMember(Value = "payment_completed")]
PaymentCompleted

実務では、内部処理ではenumを使って型安全に管理し、外部入出力では用途に応じてstringへ変換します。さらに、TryParseIsDefined、フォールバック処理、キャッシュなどを組み合わせることで、安全で保守しやすいenum変換処理を実装できます。