読者です 読者をやめる 読者になる 読者になる
ようこそ。睡眠不足なプログラマのチラ裏です。

C#で振舞いを抽象化してStateパターン的に使えるTypeSafeEnumを書いてみた。Delegate.DynamicInvokeのオーバーヘッドっていかほどなの?

プログラミング C#2.0 C#3.0 デザインパターン イディオム

以前書いた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
気が向いたのでバグの修正してみました。