enum 値に任意の名称やその他の情報を保持する方法について

仕事で行うプログラミングと自分の趣味などで行うプログラミングの間のギャップに悩む @masa711115 です。

皆さんはシステム/プログラム内で使用する定数値についてどのような管理方法を取られているでしょうか。 一般的には const や enum で管理されていると思います。 今回はこの中で enum について書いてみたいと思います。 ※ソースの内容はすべて C# です。 ※また、Unit Test の記述は Visual Studio の単体テスト機能を使用しています。


1. 巷でよく見るソース

よくあるプログラムソースの記述として以下の様なものを見ると思います。

public const string HOGE_CODE01 = "01";
public const string HOGE_NAME01 = "HOGE";
public const string HOGE_CODE02 = "02";
public const string HOGE_NAME02 = "HOGEHOGE";

主な用途としては外部にデータ化するまでもないコード/名称の組み合わせを定数として管理するというものですね。 こんなレベルの内容であれば確かにこれでもいい気はします。 しかし、ここにコード3 と名称3 が追加になるとか、説明という項目を追加したくなるとどうでしょうか?

途端に面倒になりますね。また、変更漏れや追加漏れなどミスをしやすい内容になると思います。

もちろん、このような記述をしているということはコードを名称に変換する処理もどこかに記述されているはずですしね…

私はこのパターンでは enum を使用することが多いです。 しかし、enum を使用してもコードと名称の変換処理をなくすことはできず、内容の追加や変更に対して手間がかかりますね。

2. じゃぁどうすればいいの?

.Net 1.0 ? .Net 2.0 の時期 (C# 1.0 / VB 7.0, C# 2.0 / VB 8.0) では enum にカスタム属性を定義して名称などの追加情報を保持することができました。 当時は Web 上でもこの方法をよく見掛けた気がします。

そして、Visual Studio 2008 以降のバージョンで使用可能となった C# 3.0 / VB 9.0 では拡張メソッドが使用可能となり、属性を使う記述はあまり見なくなりました。

ですが、私的には少し疑問も感じています。

・拡張メソッドを使用した記事を見るとほとんどの場合は拡張メソッド内にマジックナンバーが復活してしまっている。 ※実際に記述されたコードでは排除されているかもしれませんが… ・拡張メソッド内で変換処理を記述してしまうと上記の「巷でよく見るソース」と大して変わらない。

これでは、わざわざ const で定義するよりも多くのコードを書いて拡張メソッドを使用する意味がありません(>;_<)

なので、以下の様に記述することを考えてみました。 例はあまり良くないのですが…(^^;;

EnumExtensionAttribute クラス

using System;
using System.Collections.Generic;

using System.Reflection;

namespace XXX.Izu.Library.Utility.Enum
{

    ///;
    /// Enum拡張属性ベースクラス
    ///;
    ///;
    /// Enumが表す様々な情報をEnumとともに保持するための属性クラス
    ///;
    [AttributeUsage ( AttributeTargets.Field, AllowMultiple = false, Inherited = true )]
    public abstract class EnumExtentionAttribute : Attribute
    {

        #region " Private Field "

        ///;
        /// 拡張属性プロパティ保持用ディクショナリ
        ///;
        private readonly Dictionary<string, object> _informations;

        #endregion

        #region " Constructor "

        ///;
        /// コンストラクタ
        ///;
        ///;
        /// 派生クラスのコンストラクタから呼び出す。
        /// 3項目以上をプロパティとして保持する場合に使用
        ///;
        protected EnumExtentionAttribute ( )
        {
            _informations = new Dictionary<string, object> ( );
        }

        ///;
        /// コンストラクタ
        ///;
        ///;
        /// 派生クラスのコンストラクタから呼び出す。
        /// 単純なキー、値のペアを属性として保持する場合に使用
        ///;
        ///;
        /// Enumに付随する属性を保持したディクショナリ
        ///;
        protected EnumExtentionAttribute ( Dictionary<string, object> informations )
        {
            _informations = informations;
        }

        #endregion

        #region " Indexer "

        ///;
        /// インデクサ
        ///;
        ///;
        /// Enumに付随する各種情報を取得するために使用
        ///;
        ///;情報のキー/プロパティ名など;
        public object this [ string key ]
        {
            get
            {
                try
                {
                    return _informations [ key ];
                }
                catch ( KeyNotFoundException )
                {
                    throw new PropertyNotExistsException ( GetType ( ), key );
                }
            }
            protected set
            {
                if ( !_informations.ContainsKey ( key ) )
                {
                    _informations.Add ( key, value );
                    return;
                }

                _informations [ key ] = value;
            }
        }

        #endregion

        #region " Static Member "

        ///;
        /// Enum値の情報を取得するためのメソッド
        ///;
        ///;対象となるEnum値;
        ///;Enum値のフィールド情報;
        private static FieldInfo GetFieldInfo ( System.Enum field )
        {
            var enumType = field.GetType ( );
            var name = System.Enum.GetName ( enumType, field );
            return enumType.GetField ( name );
        }

        ///;
        /// Enum値に設定された拡張属性を取得するためのメソッド
        ///;
        ///;拡張属性の型を指定;
        ///;対象となるEnum値;
        /// ;拡張属性のインスタンス;
        /// ;指定したEnum値に拡張属性が設定されていない場合に発生;
        protected static T GetAttribute<T> ( System.Enum field ) where T : Attribute
        {
            var attr = ( T ) GetCustomAttribute ( GetFieldInfo ( field ), typeof ( T ) );
            if ( attr == null )
                throw new NotExtendedException ( typeof ( T ), field );
            return attr;
        }

        #endregion

    }
}

EnumExtensionAttribute の使用方法とテスト

・SimpleEnumExtentionAttribute と WeekDay1 ではカスタム属性を使用して enum に付属の情報を持たせる方法とその使用方法を記述しています。 ・Sample1EnumExtentionAttribute と WeekDay2 ではカスタム属性と拡張メソッドを使用して enum に付属の情報を持たせる方法とその使用方法を記述しています。

using System;
using System.Text;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using XXX.Izu.Library.Utility.Enum;
namespace TestProject.Utility
{
    #region " Enum拡張属性のシンプルな使用例 "
    #region " サンプルクラス "
    /// ;
    /// Enum拡張属性サンプルクラス(シンプルな拡張:名前、値)
    /// ;
    public class SimpleEnumExtentionAttribute : EnumExtentionAttribute
    {
        public SimpleEnumExtentionAttribute(string name, string value)
            : base(new Dictionary<string, object>() { { name, value } })
        { }
        public static SimpleEnumExtentionAttribute GetAttribute(System.Enum field)
        {
            return GetAttribute<SimpleEnumExtentionAttribute>(field);
        }
    }
    #endregion
    #region " サンプルEnum "
    public enum WeekDay1
    {
        [SimpleEnumExtention("Sunday", "日")]
        Sunday = 0,
        [SimpleEnumExtention("Monday", "月")]
        Monday = 1,
        [SimpleEnumExtention("Tuesday", "火")]
        Tuesday = 2,
        [SimpleEnumExtention("Wednesday", "水")]
        Wednesday = 3,
        [SimpleEnumExtention("Thursday", "木")]
        Thursday = 4,
        [SimpleEnumExtention("Friday", "金")]
        Friday = 5,
        [SimpleEnumExtention("Saturday", "土")]
        Saturday = 6
    }
    #endregion
    #endregion
    #region " Enum拡張属性の使用例 "
    #region " サンプルクラス "
    /// ;
    /// Enum拡張属性サンプルクラス
    /// ;
    public class Sample1EnumExtentionAttribute : EnumExtentionAttribute
    {
        public string Name
        {
            get { return base["Name"].ToString(); }
            set { base["Name"] = value; }
        }
        public string ShortName
        {
            get { return base["ShortName"].ToString(); }
            set { base["ShortName"] = value; }
        }
        public string Value
        {
            get { return base["Value"].ToString(); }
            set { base["Value"] = value; }
        }
        public static Sample1EnumExtentionAttribute GetAttribute(System.Enum field)
        {
            return GetAttribute<Sample1EnumExtentionAttribute>(field);
        }
    }
    #endregion
    #region " サンプルEnum "
    public enum WeekDay2
    {
        [Sample1EnumExtention(Name = "Sunday", ShortName = "Sun", Value = "日")]
        Sunday = 0,
        [Sample1EnumExtention(Name = "Monday", ShortName = "Mon", Value = "月")]
        Monday = 1,
        [Sample1EnumExtention(Name = "Tuesday", ShortName = "Tue", Value = "火")]
        Tuesday = 2,
        [Sample1EnumExtention(Name = "Wednesday", ShortName = "Wed", Value = "水")]
        Wednesday = 3,
        [Sample1EnumExtention(Name = "Thursday", ShortName = "Thu", Value = "木")]
        Thursday = 4,
        [Sample1EnumExtention(Name = "Friday", ShortName = "Fri", Value = "金")]
        Friday = 5,
        [Sample1EnumExtention(Name = "Saturday", ShortName = "Sat", Value = "土")]
        Saturday = 6,
        NotExtend = 7
    }
    #region " サンプルEnum Extention"
    static class WeekDay2Extension
    {
        public static string Name(this WeekDay2 self)
        {
            return Sample1EnumExtentionAttribute.GetAttribute(self).Name;
        }
        public static string ShortName(this WeekDay2 self)
        {
            return Sample1EnumExtentionAttribute.GetAttribute(self).ShortName;
        }
        public static string Value(this WeekDay2 self)
        {
            return Sample1EnumExtentionAttribute.GetAttribute(self).Value;
        }
    }
    #endregion
    #endregion
    #endregion
    /// ;
    /// UnitTest_EnumExtAttribute の概要の説明
    /// ;
    [TestClass]
    public class UnitTest_EnumExtAttribute
    {
        public UnitTest_EnumExtAttribute ( )
        {
            //
            // TODO: コンストラクタ ロジックをここに追加します
            //
        }
        private TestContext testContextInstance;
        /// ;
        ///現在のテストの実行についての情報および機能を
        ///提供するテスト コンテキストを取得または設定します。
        ///;
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }
        #region 追加のテスト属性
        //
        // テストを作成する際には、次の追加属性を使用できます:
        //
        // クラス内で最初のテストを実行する前に、ClassInitialize を使用してコードを実行してください
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // クラス内のテストをすべて実行したら、ClassCleanup を使用してコードを実行してください
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // 各テストを実行する前に、TestInitialize を使用してコードを実行してください
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // 各テストを実行した後に、TestCleanup を使用してコードを実行してください
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion
        [TestMethod]
        public void シンプル拡張属性のキー値アクセス_EQ ( )
        {
            Assert.AreEqual ( "日", SimpleEnumExtentionAttribute.GetAttribute ( WeekDay1.Sunday ) [ "Sunday" ] );
            Assert.AreEqual ( "月", SimpleEnumExtentionAttribute.GetAttribute ( WeekDay1.Monday ) [ "Monday" ] );
            Assert.AreEqual ( "火", SimpleEnumExtentionAttribute.GetAttribute ( WeekDay1.Tuesday ) [ "Tuesday" ] );
            Assert.AreEqual ( "水", SimpleEnumExtentionAttribute.GetAttribute ( WeekDay1.Wednesday ) [ "Wednesday" ] );
            Assert.AreEqual ( "木", SimpleEnumExtentionAttribute.GetAttribute ( WeekDay1.Thursday ) [ "Thursday" ] );
            Assert.AreEqual ( "金", SimpleEnumExtentionAttribute.GetAttribute ( WeekDay1.Friday ) [ "Friday" ] );
            Assert.AreEqual ( "土", SimpleEnumExtentionAttribute.GetAttribute ( WeekDay1.Saturday ) [ "Saturday" ] );
        }
        [TestMethod]
        public void シンプル拡張属性のキー値アクセス_NotEQ ( )
        {
            Assert.AreNotEqual ( "月", SimpleEnumExtentionAttribute.GetAttribute ( WeekDay1.Sunday ) [ "Sunday" ] );
            Assert.AreNotEqual ( "日", SimpleEnumExtentionAttribute.GetAttribute ( WeekDay1.Monday ) [ "Monday" ] );
        }
        [TestMethod]
        [ExpectedException ( typeof ( PropertyNotExistsException ) )]
        public void シンプル拡張属性のキー値アクセス_失敗1 ( )
        {
            Assert.AreNotEqual ( "日", SimpleEnumExtentionAttribute.GetAttribute ( WeekDay1.Sunday ) [ "Invalid" ] );
        }
        [TestMethod]
        [ExpectedException ( typeof ( NotExtendedException ) )]
        public void シンプル拡張属性のキー値アクセス_失敗2 ( )
        {
            Assert.AreNotEqual ( "日", SimpleEnumExtentionAttribute.GetAttribute ( WeekDay2.NotExtend ) [ "Invalid" ] );
        }
        [TestMethod]
        public void サンプル1拡張属性のプロパティアクセス_EQ ( )
        {
            Assert.AreEqual ( "Sunday", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Sunday ).Name );
            Assert.AreEqual ( "Monday", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Monday ).Name );
            Assert.AreEqual ( "Tuesday", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Tuesday ).Name );
            Assert.AreEqual ( "Wednesday", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Wednesday ).Name );
            Assert.AreEqual ( "Thursday", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Thursday ).Name );
            Assert.AreEqual ( "Friday", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Friday ).Name );
            Assert.AreEqual ( "Saturday", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Saturday ).Name );
            Assert.AreEqual ( "Sun", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Sunday ).ShortName );
            Assert.AreEqual ( "Mon", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Monday ).ShortName );
            Assert.AreEqual ( "Tue", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Tuesday ).ShortName );
            Assert.AreEqual ( "Wed", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Wednesday ).ShortName );
            Assert.AreEqual ( "Thu", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Thursday ).ShortName );
            Assert.AreEqual ( "Fri", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Friday ).ShortName );
            Assert.AreEqual ( "Sat", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Saturday ).ShortName );
            Assert.AreEqual ( "日", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Sunday ).Value );
            Assert.AreEqual ( "月", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Monday ).Value );
            Assert.AreEqual ( "火", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Tuesday ).Value );
            Assert.AreEqual ( "水", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Wednesday ).Value );
            Assert.AreEqual ( "木", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Thursday ).Value );
            Assert.AreEqual ( "金", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Friday ).Value );
            Assert.AreEqual ( "土", Sample1EnumExtentionAttribute.GetAttribute ( WeekDay2.Saturday ).Value );
        }
        [TestMethod]
        public void サンプル1拡張属性のプロパティアクセス_拡張メソッド経由_EQ()
        {
            Assert.AreEqual("Sunday", WeekDay2.Sunday.Name());
            Assert.AreEqual("Monday", WeekDay2.Monday.Name());
            Assert.AreEqual("Tuesday", WeekDay2.Tuesday.Name());
            Assert.AreEqual("Wednesday", WeekDay2.Wednesday.Name());
            Assert.AreEqual("Thursday", WeekDay2.Thursday.Name());
            Assert.AreEqual("Friday", WeekDay2.Friday.Name());
            Assert.AreEqual("Saturday", WeekDay2.Saturday.Name());
            Assert.AreEqual("Sun", WeekDay2.Sunday.ShortName());
            Assert.AreEqual("日", WeekDay2.Sunday.Value());
        }
    }
}

実際に使用する上では、WeekDay1 の場合ではかなり冗長だった記述が WeekDay2 ではかなりシンプルになったと思います。

3. まとめ

今回は .Net で enum 値に関連情報を色々持たせる方法について書いてみました。 const 組み合わせ派に言わせると enum 値の中身が判らないと困るらしいのですが、そんなこと意識しながらプログラミングしなければならないことが間違いだと私は考えています。意味のある名前をなぜつけるのか、そのメリット/デメリットについてもう少し真剣に考えてみましょう。 ※この場合の const と比較したデメリットはコードの記述量が少し増えるだけですよ。今どきの IDE には必ずといっていいほど存在しているスニペットの機能を使えば無視できるレベルだと思います。

P.S.

カスタム属性ではプロパティとして定義しているので本当は”()”なしで参照したいのですけれど… なんかいい方法ないですかねw

enum 値に任意の名称やその他の情報を保持する方法について” への3件のコメント

  1. public class WeekDay01
    {
    public Id { get; set; }
    public EnglishName { get; set; }
    public JapaneseName { get; set; }
    }

    public static class WeekDay01s
    {
    static readonly _Sunday = new WeekDay() { Id = 0, EnglishName = “Sunday”, JapaneseName = “日” };
    public static readonly WeekDay Sunday { get { return _Sunday; } }
    static readonly _Monday = new WeekDay() { Id = 1, EnglishName = “Monday”, JapaneseName = “月” };
    public static readonly WeekDay Monday { get { return _Monday; } }

    }

  2. public class WeekDay01 : IEquatable
    {
    public int Id { get; set; }
    public string EnglishName { get; set; }
    public string JapaneseName { get; set; }
    public bool Equals(WeekDay01 other)
    {
    return Id == other.Id
    && EnglishName == other.EnglishName
    && JapaneseName == other.JapaneseName;
    }
    }
    public sealed class WeekDay01s
    {
    static readonly WeekDay01 _Sunday = new WeekDay01() { Id = 0, EnglishName = “Sunday”, JapaneseName = “日” };
    public static WeekDay01 Sunday { get { return _Sunday; } }
    static readonly WeekDay01 _Monday = new WeekDay01() { Id = 1, EnglishName = “Monday”, JapaneseName = “月” };
    public static WeekDay01 Monday { get { return _Monday; } }
    }

    • コメントありがとうございます。
      クラスで実装する方法については、次の記事で触れさせていただきました。

      記事の内容的には私の理解不足で不十分な感じですが(^^;;

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>