C#のEnumと文字列を相互に変換する
これはInfocom Advent Calendar 2020 11日目の記事です.
最近お仕事でC#を使うことがあり,お勉強しながらやっているのですが,Enumと文字列の相互変換がどうにもスマートにできなくて困ってました. 結局,拡張メソッドとParse用のメソッドを作る方法にしたので,その方法をメモしておきます.
環境
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.15.6
BuildVersion: 19G73
$ mcs --version
Mono C# compiler version 6.12.0.90
$ mono --version
Mono JIT compiler version 6.12.0.90 (tarball Sun Oct 18 19:07:15 BST 2020)
Copyright (C) 2002-2014 Novell, Inc, Xamarin Inc and Contributors. www.mono-project.com
TLS:
SIGSEGV: altstack
Notification: kqueue
Architecture: amd64
Disabled: none
Misc: softdebug
Interpreter: yes
LLVM: supported, not enabled.
Suspend: hybrid
GC: sgen (concurrent by default)
$ swift --version
Apple Swift version 5.0 (swift-5.0-RELEASE)
Target: x86_64-apple-darwin19.6.0
課題
ビジネスモデルとして定義してるEnumを,データベース上の値にマッピングするために,文字列と相互変換する課題がありました. 普段swiftを使っているので,はじめはEnumにコンストラクタや変換メソッドを付けるアプローチを考えてみたのですが,C#の言語仕様上それは難しそうだったので,別のアプローチを考えました.
コード
こんな感じにしました.
- Enumから文字列へは拡張メソッドの仕組みで,Enumの値からアクセスできるようにする
- 文字列からEnum値を生成するのは,それ用の生成メソッドを用意する
- Enum値の宣言と同じ文字列なら
System.Enum.TryParse()
でも良いんですが,異なる文字列への相互変換を想定して,ここでは使っていません - ここが,型に閉じたものにしたかったけどできず,妥協した点です
- あと,マッピングできない文字列はNullableなEnum値にしたかったのですが,そこもうまい解法がなさそうだった(
CatBleed?
は定義できないらしい)ので,やむなく.Unknown
を作っています
- Enum値の宣言と同じ文字列なら
index.cs
using System;
// 猫種の列挙値
public enum CatBleed {
Korat,
JapaneseBobtail,
Persian,
Unknown,
}
// 猫種の列挙値と文字列の相互変換を提供する
public static class CatBleedEx {
// enum→string
public static string ToDbValue(this CatBleed catBleed)
{
switch (catBleed) {
case CatBleed.Korat:
return "KRT";
case CatBleed.JapaneseBobtail:
return "JBT";
case CatBleed.Persian:
return "PRS";
default:
return null;
}
}
// string→enum
public static CatBleed? ParseFromDbValue(string dbValue) {
switch (dbValue) {
case "KRT":
return CatBleed.Korat;
case "JBT":
return CatBleed.JapaneseBobtail;
case "PRS":
return CatBleed.Persian;
default:
return CatBleed.Unknown;
}
}
}
public class Index
{
static public void Main ()
{
// enum→string
var myCat1 = CatBleed.JapaneseBobtail;
Console.WriteLine($"myCat1 cat bleed is {myCat1.ToDbValue()}");
// string→enum(一致するenumがある)
var myCat2 = CatBleedEx.ParseFromDbValue("PRS");
if (myCat2 == CatBleed.Unknown) {
Console.WriteLine($"myCat2 is unknown");
}
else {
Console.WriteLine($"myCat2 cat bleed is {myCat2}");
}
// string→enum(一致するenumがない)
var myCat3 = CatBleedEx.ParseFromDbValue("HOGE");
if (myCat3 == CatBleed.Unknown) {
Console.WriteLine($"myCat3 is unknown");
}
else {
Console.WriteLine($"myCat3 cat bleed is {myCat3}");
}
}
}
実行
$ mcs index.cs && mono index.exe
myCat1 cat bleed is JBT
myCat2 cat bleed is Persian
myCat3 is unknown
swiftなら・・・
ちなみにswiftならこんな感じです. (相互変換する文字列が1種類ならraw valueにしてもよいんですが,複数の変換を想定してcomputed propertyにしています.)
index.swift
// 猫種
enum CatBleed {
case korat
case japaneseBobtail
case persian
// enum→String
var dbValue: String {
switch self {
case .korat:
return "KRT"
case .japaneseBobtail:
return "JBT"
case .persian:
return "PRS"
}
}
// String→enum
init?(from dbValue: String) {
switch dbValue {
case "KRT":
self = .korat
case "JBT":
self = .japaneseBobtail
case "PRS":
self = .persian
default:
return nil
}
}
}
// enum→string
let myCat1 = CatBleed.japaneseBobtail
print("myCat1 cat bleed is \(myCat1.dbValue)")
// String→enum(一致するenumがある)
let myCat2 = CatBleed(from: "PRS")
if let myCat2 = myCat2 {
print("myCat2 cat bleed is \(myCat2)")
}
else {
print("myCat2 is unknown")
}
// String→enum(一致するenumがない)
let myCat3 = CatBleed(from: "HOGE")
if let myCat3 = myCat3 {
print("myCat3 cat bleed is \(myCat3)")
}
else {
print("myCat3 is unknown")
}
実行
$ swift index.swift
myCat1 cat bleed is JBT
myCat2 cat bleed is persian
myCat3 is unknown
結果
やっぱりswift良いよね!