ottijp blog

C#のEnumと文字列を相互に変換する

2020-12-11csharpSwift

これは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を作っています
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良いよね!

refs


ottijp
Satoshi SAKAO (@ottijp)

都内でアプリケーションエンジニアをしています

...