シリアル化可能なStateパターン的に使えるタイプセーフEnum。たぶん最終形態(フリーザ様的な意味で)
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の最終形態となるでしょう(フリーザ様的な意味で)。