C#で振舞いを抽象化してStateパターン的に使えるTypeSafeEnumを書いてみた。Delegate.DynamicInvokeのオーバーヘッドっていかほどなの?
以前書いたAbstractなTypeSafeEnumでは、振舞いについてFunc
でもやっぱり、ちゃんとしたStateパターン的なTypeSafeEnumが欲しいよね。
というわけで、タイプセーフEnumの振舞いについてジェネリックで抽象化してみました。
また、id:atsukanrockさんの熱燗ロックのブログ - .NET Frameworkで、シリアル化可能なタイプセーフenumを実装するにてご指摘のあるように、
シリアライズ/デシリアライズ可能であるべきなので、SerializableAttributeを付加しておきました。
振舞いを抽象化してStateパターン的に使えるタイプセーフEnum
まだ改善の余地はありそうですが、とりあえずこんな感じにしてみました。
以下C#のサンプルです。
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace ClassLibrary1 { /// <summary> /// タイプセーフなEnum /// </summary> /// <typeparam name="T">自身を言及するために自身を渡してね</typeparam> /// <typeparam name="TAction">ReturnTypeがSystem.Voidなデリゲート(振る舞い)</typeparam> [Serializable] public abstract class TypeSafeEnum<TSelf, TAction> : IComparable<TypeSafeEnum<TSelf, TAction>>, IEquatable<TypeSafeEnum<TSelf, TAction>> where TSelf : TypeSafeEnum<TSelf, TAction> { private static Dictionary<int, TSelf> _values = new Dictionary<int, TSelf>(); protected System.MulticastDelegate _behavior = null; protected object[] _args; #region コンストラクタ /// <summary> /// デフォルトコンストラクタ /// </summary> protected TypeSafeEnum() { } /// <summary> /// コンストラクタ /// </summary> /// <param name="behavior">振舞い</param> protected TypeSafeEnum(TAction behavior) { _behavior = behavior as System.MulticastDelegate; if (_behavior == null) throw new ArgumentException("System.MulticastDelegate型でなければなりません。","behavior"); if (_behavior.Method.ReturnType != typeof(void)) throw new ArgumentException("TActionのReturnTypeはSystem.Void型でなければなりません。","behavior"); } /// <summary> /// コンストラクタ /// </summary> /// <param name="behavior">振舞い</param> /// <param name="args">TActionに対する引数パラメータ</param> protected TypeSafeEnum(TAction behavior, params object[] args) : this(behavior) { if (_behavior.Method.GetParameters().Count() != args.Count()) throw new ArgumentOutOfRangeException("args","TActionの引数の数と、指定された引数の数が異なります。"); _args = args; } #endregion #region プロパティ /// <summary> /// フィールド名を取得します。 /// </summary> private string _name = ""; public string Name { get { return _name; } } protected int _ordinalNumber = 0; /// <summary> /// 序数を取得します。 /// </summary> public int OrdinalNumber { get { return this._ordinalNumber; } } private string _literal = ""; /// <summary> /// リテラルを取得します。 /// </summary> public string Literal { get { return this._literal; } } #endregion #region メソッド /// <summary> /// 初期処理 /// </summary> public static void Initialize() { Type type = typeof(TSelf); 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 = (TSelf)field.GetValue(null); t._name = field.Name; var items = (TypeSafeEnumOrdinalNumberAttribute[])field.GetCustomAttributes(typeof(TypeSafeEnumOrdinalNumberAttribute), false); if (items.Count() == 0) throw new FieldAccessException("TypeSafeEnumOrdinalNumberAttributeが設定されていません。"); if (_values.ContainsKey(items[0].OrdinalNumber)) throw new FieldAccessException("TypeSafeEnumOrdinalNumberAttributeの値が重複しています。"); _values.Add(items[0].OrdinalNumber, t); t._ordinalNumber = items[0].OrdinalNumber; t._literal = items[0].Literal; } } /// <summary> /// 振る舞い /// </summary> /// <returns></returns> public void BehaviorInvoke() { if (_behavior == null) return; if (_args == null) { _behavior.DynamicInvoke(); } else { _behavior.DynamicInvoke(_args); } } /// <summary> /// 序数に対する値を取得します。 /// </summary> /// <param name="ordinalNumber"></param> /// <returns></returns> public static TSelf ValueOf(int ordinalNumber) { if (_values.ContainsKey(ordinalNumber)) return _values[ordinalNumber]; throw new ArgumentOutOfRangeException(); } /// <summary> /// TypeSafeEnumのすべての値のコレクションを取得します。 /// </summary> /// <returns></returns> public static IEnumerable<TSelf> 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) { if (!(obj is TypeSafeEnum<TSelf, TAction>)) return false; return this.Equals((TypeSafeEnum<TSelf, TAction>)obj); } public override int GetHashCode() { return this._ordinalNumber.GetHashCode(); } #endregion #region 演算子オーバーロード public static bool operator >(TypeSafeEnum<TSelf, TAction> x, TypeSafeEnum<TSelf, TAction> y) { return (x.CompareTo(y) > 0); } public static bool operator <(TypeSafeEnum<TSelf, TAction> x, TypeSafeEnum<TSelf, TAction> y) { return (x.CompareTo(y) < 0); } public static bool operator >=(TypeSafeEnum<TSelf, TAction> x, TypeSafeEnum<TSelf, TAction> y) { return (x.CompareTo(y) >= 0); } public static bool operator <=(TypeSafeEnum<TSelf, TAction> x, TypeSafeEnum<TSelf, TAction> y) { return (x.CompareTo(y) <= 0); } public static bool operator ==(TypeSafeEnum<TSelf, TAction> x, TypeSafeEnum<TSelf, TAction> y) { return x.Equals(y); } public static bool operator !=(TypeSafeEnum<TSelf, TAction> x, TypeSafeEnum<TSelf, TAction> y) { return (!x.Equals(y)); } #endregion #region IComparable<T> メンバ public int CompareTo(TypeSafeEnum<TSelf, TAction> other) { return this._ordinalNumber - other._ordinalNumber; } #endregion #region IEquatable<T> メンバ public bool Equals(TypeSafeEnum<TSelf, TAction> other) { return (this._ordinalNumber == other._ordinalNumber); } #endregion } /// <summary> /// タイプセーフなEnum /// </summary> /// <typeparam name="T">自身を言及するために自身を渡してね</typeparam> /// <typeparam name="TAction">ReturnTypeがSystem.Voidではないデリゲート(振る舞い)</typeparam> /// <typeparam name="TResult">TFuncのReturnTypeと同じ型を指定</typeparam> [Serializable] public abstract class TypeSafeEnum<TSelf, TFunc, TResult> : TypeSafeEnum<TSelf, TFunc> where TSelf : TypeSafeEnum<TSelf, TFunc, TResult> { #region コンストラクタ /// <summary> /// コンストラクタ /// </summary> /// <param name="behavior">振舞い</param> public TypeSafeEnum(TFunc behavior) : base() { _behavior = behavior as System.MulticastDelegate; if (_behavior == null) throw new ArgumentException("System.MulticastDelegate型でなければなりません。", "behavior"); if (_behavior.Method.ReturnType == typeof(void)) throw new ArgumentException("TFuncのReturnTypeにSystem.Void型は指定できません。", "behavior"); if (_behavior.Method.ReturnType != typeof(TResult)) throw new ArgumentException("TFuncのReturnTypeとTResultの型は等しくなければなりません。", "behavior"); } /// <summary> /// コンストラクタ /// </summary> /// <param name="behavior">振舞い</param> /// <param name="args">TFuncに対する引数パラメータ</param> public TypeSafeEnum(TFunc behavior, params object[] args) : this(behavior) { if (_behavior.Method.GetParameters().Count() != args.Count()) throw new ArgumentOutOfRangeException("args", "TActionの引数の数と、指定された引数の数が異なります。"); _args = args; } #endregion /// <summary> /// 振る舞い /// </summary> /// <returns></returns> public new TResult BehaviorInvoke() { if (_behavior == null) return default(TResult); object result = null; if (_args == null) { result = _behavior.DynamicInvoke(); } else { result = _behavior.DynamicInvoke(_args); } return (TResult)result; } #region オーバーライド public override bool Equals(object obj) { if (!(obj is TypeSafeEnum<TSelf, TFunc, TResult>)) return false; return this.Equals((TypeSafeEnum<TSelf, TFunc, TResult>)obj); } public override int GetHashCode() { return this._ordinalNumber.GetHashCode(); } #endregion #region 演算子オーバーロード public static bool operator >(TypeSafeEnum<TSelf, TFunc, TResult> x, TypeSafeEnum<TSelf, TFunc, TResult> y) { return (x.CompareTo(y) > 0); } public static bool operator <(TypeSafeEnum<TSelf, TFunc, TResult> x, TypeSafeEnum<TSelf, TFunc, TResult> y) { return (x.CompareTo(y) < 0); } public static bool operator >=(TypeSafeEnum<TSelf, TFunc, TResult> x, TypeSafeEnum<TSelf, TFunc, TResult> y) { return (x.CompareTo(y) >= 0); } public static bool operator <=(TypeSafeEnum<TSelf, TFunc, TResult> x, TypeSafeEnum<TSelf, TFunc, TResult> y) { return (x.CompareTo(y) <= 0); } public static bool operator ==(TypeSafeEnum<TSelf, TFunc, TResult> x, TypeSafeEnum<TSelf, TFunc, TResult> y) { return x.Equals(y); } public static bool operator !=(TypeSafeEnum<TSelf, TFunc, TResult> x, TypeSafeEnum<TSelf, TFunc, TResult> y) { return (!x.Equals(y)); } #endregion #region IComparable<T> メンバ public int CompareTo(TypeSafeEnum<TSelf, TFunc, TResult> other) { return this._ordinalNumber - other._ordinalNumber; } #endregion #region IEquatable<T> メンバ public bool Equals(TypeSafeEnum<TSelf, TFunc, TResult> other) { return (this._ordinalNumber == other._ordinalNumber); } #endregion } }
TypeSafeEnumOrdinalNumberAttribute
振舞いとは別に、リテラルを含めることができるようにしてみました。
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> private string _leteral = ""; public string Literal { get { return this._leteral; } } /// <summary> /// コンストラクタ /// </summary> /// <param name="ordinalNumber">序数</param> public TypeSafeEnumOrdinalNumberAttribute(int ordinalNumber) { this._ordinalNumber = ordinalNumber; } /// <summary> /// コンストラクタ /// </summary> /// <param name="ordinalNumber">序数</param> /// <param name="literal">リテラル</param> public TypeSafeEnumOrdinalNumberAttribute(int ordinalNumber, string literal) : this(ordinalNumber) { this._leteral = literal; } } }
TypeSafeEnumを継承してみましょう
お試し
using System; using System.IO; using System.Linq; using System.Runtime.Serialization.Formatters.Binary; 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}\t\t{3}", s.OrdinalNumber, s.Name, s.Literal, s.BehaviorInvoke()); } Console.WriteLine(); if (sato.ElementAt(0) != NarutoNoSato.Suna) { System.Console.WriteLine("{0}と{1}は違うよ!", sato.ElementAt(0).Literal, NarutoNoSato.Suna.Literal); } if (sato.ElementAt(0) == NarutoNoSato.Konoha) { System.Console.WriteLine("{0}と{1}は等しいよ!", sato.ElementAt(0).Literal, NarutoNoSato.Konoha.Literal); } Console.WriteLine(); var ame = DeepCopyClone<NarutoNoSato>(NarutoNoSato.Ame); Console.WriteLine("[{0}] {1} \t\t{2}\t\t{3}",ame.OrdinalNumber, ame.Name, ame.Literal, ame.BehaviorInvoke()); } finally { Console.ReadLine(); } } /// <summary> /// 対象オブジェクトのディープコピークローンを取得します。 /// </summary> /// <typeparam name="T">対象オブジェクトの型</typeparam> /// <param name="target">コピー対象オブジェクトを指定します。</param> /// <returns>ディープコピーのクローンを返します。</returns> /// <remarks>このメソッでディープコピーするには、対象クラスがシリアライズ可能である必要があります。</remarks> public static T DeepCopyClone<T>(T target) { object clone = null; using (MemoryStream stream = new MemoryStream()) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream, target); stream.Position = 0; clone = formatter.Deserialize(stream); } return (T)clone; } } /// <summary> /// NARUTOの里 /// </summary> [Serializable] public sealed class NarutoNoSato : TypeSafeEnum<NarutoNoSato, Func<string>, string> { private NarutoNoSato(Func<string> behavior) : base(behavior){} [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 星隠れの里 ツマル 木の葉隠れの里と砂隠れの里は違うよ! 木の葉隠れの里と木の葉隠れの里は等しいよ! [64] Ame 雨隠れの里 半蔵
という感じで、ジェネリックだが・・・、あえてボックス化する!という手法を取っています。(delegateだもの・・・)
Concreteクラス側では、行う処理をそのまま振舞いとして設定することもできますし、
あるいは、戻り値としてインターフェイスを返すデリゲートで作成すれば、
あらまあタイプセーフEnumがそのままStateパターンとして利用できちゃうみたいな?夢は広がりんぐですね(´∀`*)
そして問題のDelegate.DynamicInvokeですが、さほど神経質に気にする必要はない気はしていますが、
Delegate.DynamicInvokeのオーバーヘッドって、実際のところいかほどのものなのでしょうか。教えてえろい人!
なお、いつものことですが厳密なテストはしていません。ご了承ください。
追記:2009.06.27
仕事でVBに移植してみたんですが、普通のenumとの比較コードを書いてみたら、
案の定バグっていました。気が向いたら修正して再うpします。
追記:2009.07.06
気が向いたのでバグの修正してみました。