C#でTypeSafeEnumを書いてみた
タイプセーフenumパターンというのがあったのですね。知りませんでした。
ジョシュア ・ブロック氏が「Effective Java」という本の中で提案していたものらしいです*1。
いやはや、もっと幅広く勉強せんとなぁ・・。ということで、
id:bleis-tiftさんのエントリを参考にして、C#でタイプセーフなenumをちょっくら書いてみました。
http://d.hatena.ne.jp/bleis-tift/20080808/1218133517
AbstractなTypeSafeEnumを作る
ということで、さっそくAbstractなTypeSafeEnumたぶん、こんな感じでよいでしょう。
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace ClassLibrary1 { /// <summary> /// タイプセーフなEnum /// </summary> /// <typeparam name="T">自身を言及するために自身の型指定をしてね</typeparam> public abstract class TypeSafeEnum<T> :IComparable<TypeSafeEnum<T>>, IEquatable<TypeSafeEnum<T>> where T : TypeSafeEnum<T> { private static Dictionary<int, T> _values = new Dictionary<int, T>(); private Func<string> _method = () => ""; #region コンストラクタ /// <summary> /// コンストラクタ /// </summary> /// <param name="method">リテラル文字列を返すFunc</param> protected TypeSafeEnum(Func<string> method){ this._method = method; } #endregion #region プロパティ private string _name = ""; public string Name { get { return _name; } } private int _ordinalNumber = 0; public int OrdinalNumber { get { return this._ordinalNumber; } } #endregion #region メソッド /// <summary> /// 初期処理 /// </summary> public static void Initialize() { Type type = typeof(T); var fields = from f in type.GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) where !f.IsLiteral select f; foreach (var field in fields) { var t = (T)field.GetValue(null); t._name = field.Name; var items = (TypeSafeEnumOrdinalNumberAttribute[])field.GetCustomAttributes(typeof(TypeSafeEnumOrdinalNumberAttribute), false); if (items.Count() == 0) throw new FieldAccessException(); if (!_values.ContainsKey(items[0].OrdinalNumber)) _values.Add(items[0].OrdinalNumber, t); t._ordinalNumber = items[0].OrdinalNumber; } } /// <summary> /// リテラル文字列を取得します。 /// </summary> /// <returns></returns> public string GetLiteral() { return _method(); } /// <summary> /// 数値からTypeSafeEnum(T)を取得します。 /// </summary> /// <param name="id"></param> /// <returns></returns> public static T ValueOf(int id) { if (_values.ContainsKey(id)) return _values[id]; throw new ArgumentOutOfRangeException(); } /// <summary> /// TypeSafeEnumのすべての値のコレクションを取得します。 /// </summary> /// <returns></returns> public static IEnumerable<T> GetValues() { if (_values.Count == 0) Initialize(); foreach (var v in _values) yield return v.Value; } /// <summary> /// TypeSafeEnumのすべてのキーのコレクションを取得します。 /// </summary> /// <returns></returns> public static IEnumerable<int> GetKeys() { if (_values.Count == 0) Initialize(); foreach (var v in _values) yield return v.Key; } #endregion #region オーバーライド public override string ToString() { return this._name; } public override bool Equals(object obj) { return this.Equals((TypeSafeEnum<T>)obj); } public override int GetHashCode() { return this._ordinalNumber.GetHashCode(); } #endregion #region 演算子オーバーロード public static bool operator >(TypeSafeEnum<T> x, TypeSafeEnum<T> y) { return (x.CompareTo(y) > 0); } public static bool operator <(TypeSafeEnum<T> x, TypeSafeEnum<T> y) { return (x.CompareTo(y) < 0); } public static bool operator >=(TypeSafeEnum<T> x, TypeSafeEnum<T> y) { return (x.CompareTo(y) >= 0); } public static bool operator <=(TypeSafeEnum<T> x, TypeSafeEnum<T> y) { return (x.CompareTo(y) <= 0); } public static bool operator ==(TypeSafeEnum<T> x, TypeSafeEnum<T> y) { return x.Equals(y); } public static bool operator !=(TypeSafeEnum<T> x, TypeSafeEnum<T> y) { return (!x.Equals(y)); } #endregion #region IComparable<T> メンバ public int CompareTo(TypeSafeEnum<T> other) { return this._ordinalNumber - other._ordinalNumber; } #endregion #region IEquatable<T> メンバ public bool Equals(TypeSafeEnum<T> other) { return (this._ordinalNumber == other._ordinalNumber); } #endregion } }
OrdinalNumberを指定するTypeSafeEnumOrdinalNumberAttribute
続きまして、OrdinalNumberを指定するための属性を。
using System; namespace ClassLibrary1 { /// <summary> /// TypeSafeEnumのOrdinalNumberを設定する属性 /// </summary> [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] public class TypeSafeEnumOrdinalNumberAttribute : Attribute { /// <summary>序数</summary> private int _ordinalNumber; public int OrdinalNumber { get { return this._ordinalNumber; } } /// <summary> /// コンストラクタ /// </summary> /// <param name="ordinalNumber">序数</param> public TypeSafeEnumOrdinalNumberAttribute(int ordinalNumber) { this._ordinalNumber = ordinalNumber; } } }
TypeSafeEnumを継承してみましょう。
では、実際にタイプセーフなEnumを使ってみましょう。
using System; using System.Linq; using ClassLibrary1; namespace ConsoleApplication1 { class Program { static void Main() { try { foreach (var i in NarutoNoSato.GetKeys()) { System.Console.WriteLine("[{0}] {1}",i.ToString(),NarutoNoSato.ValueOf(i)); } Console.WriteLine(); var sato = NarutoNoSato.GetValues(); foreach (var s in sato) { System.Console.WriteLine("[{0}] {1} \t\t{2}", s.OrdinalNumber, s.Name, s.GetLiteral()); } Console.WriteLine(); if (sato.ElementAt(0) != NarutoNoSato.Suna) { System.Console.WriteLine("{0}と{1}は違うよ!", sato.ElementAt(0).GetLiteral(), NarutoNoSato.Suna.GetLiteral()); } if (sato.ElementAt(0) == NarutoNoSato.Konoha) { System.Console.WriteLine("{0}と{1}は等しいよ!", sato.ElementAt(0).GetLiteral(), NarutoNoSato.Konoha.GetLiteral()); } } finally { Console.ReadLine(); } } } /// <summary> /// NARUTOの里 /// </summary> public sealed class NarutoNoSato : TypeSafeEnum<NarutoNoSato> { private NarutoNoSato(Func<string> method) : base(method){} static NarutoNoSato() { Initialize(); } [TypeSafeEnumOrdinalNumberAttribute(0x0000)] public static readonly NarutoNoSato Konoha = new NarutoNoSato(() => "木ノ葉隠れの里"); [TypeSafeEnumOrdinalNumberAttribute(0x0001)] public static readonly NarutoNoSato Suna = new NarutoNoSato(() => "砂隠れの里"); [TypeSafeEnumOrdinalNumberAttribute(0x0002)] public static readonly NarutoNoSato Kiri = new NarutoNoSato(() => "霧隠れの里"); [TypeSafeEnumOrdinalNumberAttribute(0x0004)] public static readonly NarutoNoSato Iwa = new NarutoNoSato(() => "岩隠れの里"); [TypeSafeEnumOrdinalNumberAttribute(0x0008)] public static readonly NarutoNoSato Kumo = new NarutoNoSato(() => "雲隠れの里"); [TypeSafeEnumOrdinalNumberAttribute(0x0010)] public static readonly NarutoNoSato Oto = new NarutoNoSato(() => "音隠れの里"); [TypeSafeEnumOrdinalNumberAttribute(0x0020)] public static readonly NarutoNoSato Kusa = new NarutoNoSato(() => "草隠れの里"); [TypeSafeEnumOrdinalNumberAttribute(0x0040)] public static readonly NarutoNoSato Ame = new NarutoNoSato(() => "雨隠れの里"); [TypeSafeEnumOrdinalNumberAttribute(0x0080)] public static readonly NarutoNoSato Taki = new NarutoNoSato(() => "滝隠れの里"); [TypeSafeEnumOrdinalNumberAttribute(0x0100)] public static readonly NarutoNoSato Yu = new NarutoNoSato(() => "湯隠れの里"); [TypeSafeEnumOrdinalNumberAttribute(0x0200)] public static readonly NarutoNoSato Hoshi = new NarutoNoSato(() => "星隠れの里"); } }
実行結果
[0] Konoha
[1] Suna
[2] Kiri
[4] Iwa
[8] Kumo
[16] Oto
[32] Kusa
[64] Ame
[128] Taki
[256] Yu
[512] Hoshi
[0] Konoha 木ノ葉隠れの里
[1] Suna 砂隠れの里
[2] Kiri 霧隠れの里
[4] Iwa 岩隠れの里
[8] Kumo 雲隠れの里
[16] Oto 音隠れの里
[32] Kusa 草隠れの里
[64] Ame 雨隠れの里
[128] Taki 滝隠れの里
[256] Yu 湯隠れの里
[512] Hoshi 星隠れの里
木ノ葉隠れの里と砂隠れの里は違うよ!
木ノ葉隠れの里と木ノ葉隠れの里は等しいよ!
うん。できてるみたいですね(・ω・)
とういうか、今更なんだろうけどEffective Java読まなきゃだな・・。
いろいろためになりそうなことが載ってそう。
*1:後にJavaSE5.0で採用されたみたいです