2016年9月1日木曜日

C#でカスタム属性と、拡張メソッド(Enum(列挙体)に属性を追加)

C#でカスタム属性と、拡張メソッド(Enum(列挙体)に属性を追加)



今日は、C#でカスタム属性と、拡張メソッドのお話です。

C#で、Enumに付随する情報を持つ場合に、どのように実装していますか?
Switch文や、IF文で、Enumの情報を返すメソッドを書いたりしていませんか。

ついでに、Enum値を追加したのに、Switch文に追加を忘れて、バグ!!!なんてなったことありませんか。

そんな時には、これ!
本日限りの大特価です!!!

Enumの個所に情報を書いてしまいましょう。(多少実行速度は落ちますが・・・)

namespace Sample
{
 
    //===========================================================
    /// <summary>
    /// 情報保持用カスタム属性
    /// </summary>
    //===========================================================
    [AttributeUsage(System.AttributeTargets.Field)]
    public class DescriptionAttribute : Attribute
    {
        public String Name { get; set; }
        public Int32 Price { get; set; }
        public DescriptionAttribute(String name, Int32 price)
        {
            this.Name = name;
            this.Price = price;
        }
    }
    /// <summary>
    //===========================================================
    /// フルーツ列挙体
    /// </summary>
    //===========================================================
    public enum Fruits
    {
        [Description("メロン", 10000)]
        Melon,
        [Description("みかん", 100)]
        Orange,
        [Description("りんご", 300)]
        Apple,
    }
    //===========================================================
    /// <summary>
    /// フルーツ列挙体用拡張メソッド
    /// </summary>
    //===========================================================
    public static class FruitsExtension
    {
        public static R GetProperty<R, V, T>(V enumValue, String propertyName)
            where T : Attribute
        {
            var val = default(R);
            var enumType = enumValue.GetType();
            var enumName = Enum.GetName(enumType, enumValue);
            var attrs = enumType.GetField(enumName).GetCustomAttributes(typeof(T), false);
            foreach (Object attr in attrs)
            {
                var description = null as T;
                description = attr as T;
                if (description != null)
                {
                    val = (R)description.GetType().GetProperty(propertyName).GetValue(description);
                    break;
                }
            }
            return val;
        }
        public static String Name(this Fruits fruits)
        {
            return GetProperty<String, Fruits, DescriptionAttribute>(fruits, "Name");
        }
        public static Int32 Price(this Fruits fruits)
        {
            return GetProperty<Int32, Fruits, DescriptionAttribute>(fruits, "Price");
        }
    }
    //===========================================================
    /// <summary>
    /// 実行サンプル
    /// </summary>
    //===========================================================
    public class Sample
    {
        public void Main(string[] args)
        {
            ViewFruits(Fruits.Melon);
            ViewFruits(Fruits.Orange);
            ViewFruits(Fruits.Apple);
        }
        public void ViewFruits(Fruits furits)
        {
            var name = furits.Name();
            var price = furits.Price();
            Console.WriteLine(String.Format("フルーツ:{0}\t値段:{1}",name, price));
        }
    }
}


解説

カスタム属性

    [AttributeUsage(System.AttributeTargets.Field)]
    public class DescriptionAttribute : Attribute
    {
        public String Name { get; set; }
        public Int32 Price { get; set; }
        public DescriptionAttribute(String name, Int32 price)
        {
            this.Name = name;
            this.Price = price;
        }
    }
カスタム属性のクラスです。Enumの項目に表示用の名前(Name)と値段(Price)を持たせるようにしています。

列挙体へカスタム属性を追加


    public enum Fruits
    {
        [Description("メロン", 10000)]
        Melon,
        [Description("みかん", 100)]
        Orange,
        [Description("りんご", 300)]
        Apple,
    }
Enumに属性を追加しています。
こうしておけば、新しくEnum値を追加するときでも忘れることはありません。
ここまでしておいて、属性を追加しない人は、意図的に不具合を埋め込んで
残業代を稼ごうとしているせこい人しかいません。


拡張メソッド

    public static class FruitsExtension
    {
        public static R GetProperty<R, V, T>(V enumValue, String propertyName)
            where T : Attribute
        {
            var val = default(R);
            var enumType = enumValue.GetType();
            var enumName = Enum.GetName(enumType, enumValue);
            var attrs = enumType.GetField(enumName).GetCustomAttributes(typeof(T), false);
            foreach (Object attr in attrs)
            {
                var description = null as T;
                description = attr as T;
                if (description != null)
                {
                    val = (R)description.GetType().GetProperty(propertyName).GetValue(description);
                    break;
                }
            }
            return val;
        }
        public static String Name(this Fruits fruits)
        {
            return GetProperty<String, Fruits, DescriptionAttribute>(fruits, "Name");
        }
        public static Int32 Price(this Fruits fruits)
        {
            return GetProperty<Int32, Fruits, DescriptionAttribute>(fruits, "Price");
        }
    }
 この拡張メソッドは、作成しなくてもよいかもしれませんが、毎回GetCustomAttributesってやるのが面倒なので、書いています。ジェネリックのGetPropertyは、Enum用に汎かしていますので、
別のカスタム属性を書く場合でも、利用可能です。


0 件のコメント:

コメントを投稿