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

シリアル化可能なStateパターン的に使えるタイプセーフEnum。たぶん最終形態(フリーザ様的な意味で)

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

id:atsukanrockさん、トラックバックありがとうございます。
熱燗ロックのブログ - .NET Frameworkで、シリアル化可能なタイプセーフenumを実装する - その2」を受けまして、
さっそく、まねまねさせて頂まして、再びTypeSafeEnumを書き直しました。


そうですよね。IObjectReferenceインターフェイスを利用すれば、静的クラスのシングルトンが表現できますものね。
ちなみに、IObjectReferenceインターフェイスを実装する静的クラスのシングルトンヘルパーとして作用するクラスに、
ISerializableインターフェイスを実装する必要はないと思います。SerializableAttributeさえ付加しておけばよいのではないでしょうか。


atsukanrockさんのおかげで、いい感じに実用的なタイプセーフEnumを書くことができました。
私も感謝しています。どうもありがとうございました。ピコピコさんもありがとうございました^^


以下C#のサンプルです。(もちろん、VB.NETでも同じ機能を満たすものを書くことができます)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;

namespace ClassLibrary1
{
    /// <summary>
    /// タイプセーフなEnum
    /// </summary>
    /// <typeparam name="T">自身を言及するために自身を渡してね</typeparam>
    /// <typeparam name="TAction">ReturnTypeがSystem.Voidなデリゲート(振る舞い)</typeparam>
    [Serializable]
    public abstract class TypeSafeEnum<TSelf, TAction> :
        ISerializable,
        IComparable<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;

        private static readonly object _lockObject = new object();
        private static bool _initialized = false;

        #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()
        {
            lock (_lockObject)
            {
                if (_initialized) return;
                _initialized = true;

                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 int GetHashCode()
        {
            return this._ordinalNumber.GetHashCode();
        }
        #endregion

        #region IComparable<T> メンバ
        public int CompareTo(TypeSafeEnum<TSelf, TAction> other)
        {
            return this._ordinalNumber - other._ordinalNumber;
        }
        #endregion

        /// <summary>
        /// シングルトンヘルパー
        /// </summary>
        /// <typeparam name="TReference"></typeparam>
        [Serializable]
        protected sealed class SingletonSerializationHelper<TReference> : IObjectReference
            where TReference : TypeSafeEnum<TSelf, TAction>
        {
            private readonly int _ordinalNumber = 0;

            Object IObjectReference.GetRealObject(StreamingContext context)
            {
                return ValueOf(_ordinalNumber);
            }
        }

        #region ISerializable メンバ

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("_ordinalNumber", _ordinalNumber); 
            info.SetType(typeof(SingletonSerializationHelper<TSelf>));
        }

        #endregion
    }

    /// <summary>
    /// タイプセーフなEnum
    /// </summary>
    /// <typeparam name="T">自身を言及するために自身を渡してね</typeparam>
    /// <typeparam name="TFunc">ReturnTypeがSystem.Voidではないデリゲート(振る舞い)</typeparam>
    /// <typeparam name="TResult">TFuncのReturnTypeと同じ型を指定</typeparam>
    [Serializable]
    public abstract class TypeSafeEnum<TSelf, TFunc, TResult> :
        TypeSafeEnum<TSelf, TFunc>, ISerializable 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 int GetHashCode()
        {
            return this._ordinalNumber.GetHashCode();
        }

        #endregion

        #region IComparable<T> メンバ
        public int CompareTo(TypeSafeEnum<TSelf, TFunc, TResult> other)
        {
            return this._ordinalNumber - other._ordinalNumber;
        }
        #endregion

        /// <summary>
        /// シングルトンヘルパー
        /// </summary>
        /// <typeparam name="TReference"></typeparam>
        [Serializable]
        protected sealed new class SingletonSerializationHelper<TReference> : IObjectReference
            where TReference : TypeSafeEnum<TSelf, TFunc, TResult>
        {
            private readonly int _ordinalNumber = 0;

            Object IObjectReference.GetRealObject(StreamingContext context)
            {
                return ValueOf(_ordinalNumber);
            }
        }

        #region ISerializable メンバ

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("_ordinalNumber", _ordinalNumber);
            info.SetType(typeof(SingletonSerializationHelper<TSelf>));
        }

        #endregion
    }
}
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;
        }
    }
}
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());



                Console.WriteLine();

                Console.WriteLine(NarutoNoSato.Ame == NarutoNoSato.Konoha);
                Console.WriteLine(NarutoNoSato.Konoha == NarutoNoSato.Konoha);
                
                Console.WriteLine(NarutoNoSato.Ame.Equals(NarutoNoSato.Konoha));
                Console.WriteLine(NarutoNoSato.Konoha.Equals(NarutoNoSato.Konoha));

                Console.WriteLine(NarutoNoSato.Konoha.Equals("aaa"));
                
                object o = DeepCopyClone(NarutoNoSato.Kumo);
                Console.WriteLine(NarutoNoSato.Kumo.Equals(o));
                Console.WriteLine(NarutoNoSato.Suna == o);
                Console.WriteLine(NarutoNoSato.Kumo == o);

                Console.WriteLine(((NarutoNoSato)o).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){}

        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             星隠れの里              ツマル

木の葉隠れの里と砂隠れの里は違うよ!
木の葉隠れの里と木の葉隠れの里は等しいよ!

[64] Ame                雨隠れの里              半蔵

False
True
False
True
False
True
False
True
キラービー


たぶん、これが私のブログで紹介するタイプセーフEnumの最終形態となるでしょう(フリーザ様的な意味で)。