C#でタイプセーフEnumパターン
C#でのテストはちょっとお休みして、今日はC#でタイプセーフEnumパターンの実現方法をいくつか紹介する。
C#には十分にタイプセーフなenumがあるが、それでも基本は数値型であり、switchやifによる分岐が必要になってしまう点はC言語やC++のenumと何ら変わらない。
ということで、C#でタイプセーフEnumパターンを実装することには意味がある。もちろん、C#本来のenumで十分な場合も多いので、使うか使わないかはの判断は必要だ。
まずは、C#のenumに対して全く利点のない単純なものから。
public sealed class Enum1 { private Enum1() {} public static readonly Enum1 Value1 = new Enum1(); public static readonly Enum1 Value2 = new Enum1(); }
これは単に、継承を禁止してコンストラクタをprivateにしただけのものだが、出発点としては十分な例でもある。
次は、C#のenumの欠点の一つである、文字列化が面倒という点を解消するのに十分なものだ。
public sealed class Enum2 { public readonly string Str; private Enum2(string str) { Str = str; } public override string ToString() { return Str; } public static readonly Enum2 Value1 = new Enum2("value 1"); public static readonly Enum2 Value2 = new Enum2("value 2"); }
基本的には上の例にpublic readonlyなフィールドを追加し、ToStringをオーバーライドしただけである。
Strはprivateにしたり、読み取り専用のプロパティにしたりと、いくつかの変形が考えられる。
次は、要素間に順序を追加するもので、これだけならC#のenumの劣化コピーでしかない*1が、ほかのものと組み合わせると便利だ。
public sealed class Enum3 { private static int nextOrdinal = 0; public readonly int Ordinal = nextOrdinal++; private Enum3() {} public static readonly Enum3 Value1 = new Enum3(); public static readonly Enum3 Value2 = new Enum3(); }
ちなみに、この例はEffective Javaの「順序に基づくタイプセーフenum」を参考にしている。単純化するためにIComparable
次は、数値からタイプセーフEnumを取り出せるような拡張だ。
public sealed class Enum4 { private static int nextId = 0; private static Dictionary<int, Enum4> values = new Dictionary<int, Enum4>(); private Enum4() { values.Add(nextId++, this); } public static Enum4 ValueOf(int id) { if (values.ContainsKey(id)) return values[id]; throw new ArgumentOutOfRangeException(); } public static readonly Enum4 Value1 = new Enum4(); public static readonly Enum4 Value2 = new Enum4(); }
見つからなかった場合はnullを返したり、Null Objectを返す*2といった変形が考えられる。
この例ではDictionaryである必要はない*3が、idが連番ではない場合等にはDictionaryが必要となる。
public sealed class Enum5 { private delegate void ProcDelegate(); private ProcDelegate proc; private Enum5(ProcDelegate proc) { this.proc = proc; } public void Proc() { proc(); } public static readonly Enum5 Value1 = new Enum5(delegate { Console.WriteLine("hoge"); }); public static readonly Enum5 Value2 = new Enum5(delegate { Console.WriteLine("piyo"); }); }
void Proc(Enum5 e)
{
e.Proc();
}
Javaではanonymous classで実現しているところを、C#版では匿名メソッドを使用している。
さらにこれを発展させ、呼び出し元で処理を決定できるようにすることも可能である*5。
public sealed class Enum6 { public delegate void ProcDelegate(); private delegate void DispatchDelegate(ProcDelegate proc1, ProcDelegate proc2); private DispatchDelegate dispatcher; private Enum6(DispatchDelegate dispatcher) { this.dispatcher = dispatcher; } public void Proc(ProcDelegate proc1, ProcDelegate proc2) { dispatcher(proc1, proc2); } public static readonly Enum6 Value1 = new Enum6(delegate(ProcDelegate proc1, ProcDelegate proc2) { proc1(); }); public static readonly Enum6 Value2 = new Enum6(delegate(ProcDelegate proc1, ProcDelegate proc2) { proc2(); }); }
void Proc(Enum6 e) { e.Proc( delegate { Console.WriteLine("value 1"); }, delegate { Console.WriteLine("value 2"); } ); }
最後は、グルーピング。
public sealed class Enum7 { private Enum7() {} public static class Inner1 { public static readonly Enum7 Value1 = Enum7(); public static readonly Enum7 Value2 = Enum7(); } public static class Inner2 { public static readonly Enum7 Value1 = Enum7(); public static readonly Enum7 Value2 = Enum7(); } }
privateなコンストラクタをインナークラスから呼び出しているのがポイントだ。
以上、タイプセーフEnumパターンのみ7つでした。