Enumにカスタム属性を使ってint型以外の値を返せるようにする


いつ、どこで書いたかすっかり忘れたけれど、
頭の片隅から消えないうちに書き留めておきたいと思います。

上から順に以下の物を作っていきます。

1. カスタム属性
2. 拡張メソッド
3. Enumの定義
4. 使用感

はじめは、準備から

# 1. カスタム属性

```csharp:Ready.cs
using System;

[AttributeUsage( AttributeTargets.Field )]
public class SubValueAttribute : Attribute
{
    public SubValueAttribute( object value )
    {
        this.SubValue = value;
    }

    public object SubValue { get; private set; }
}
```

作りはシンプルで、Attributeを継承したクラスを作り、アクセサとなるプロパティとミューテータを用意してあげればいいだけです。
今回はEnumのフィールドに付けたいので適用対象を「AttributeTargets.Field」としています。

# 2. 拡張メソッド

```csharp:Ready.cs
using System.Linq;
using System.Reflection;

public static class EnumEx
{
    public static T ToSubValue<T>( this Enum enm )
    {
        return (T) enm.GetType()
           .GetRuntimeField( enm.ToString() )
           .GetCustomAttributes( typeof( SubValueAttribute ), false )
           .Cast<SubValueAttribute>()
           .Single()
           .SubValue;
    }
}
```



長々とチェーンメソッドで書いていますが、やっている事はフィールドに設定した属性の値を任意(T)の型にキャストして返しているだけです。

# 3. Enumの定義

```csharp:TrySample.cs

// こんな形でEnumに属性を付けます。
enum Example
{
    [SubValue( "ABCDEFG" )]
    StringValue = 0,

    [SubValue( 12345 )]
    IntValue,

    [SubValue( new[] { "A", "B", "C" } )]
    ArrayValue,
}
```

作っておいた属性を装着して、設定値を定義しておきます。

ここまでが準備で、後はこれらを使うだけになります。

# 4. 使用感

```csharp:TrySample.cs
static void Main()
{
    // こんな感じで取れます
    string   stringValue     = Example.StringValue.ToSubValue<string>();
    int      intValue        = Example.IntValue.ToSubValue<int>();
    string[] arrayValue      = Example.ArrayValue.ToSubValue<string[]>();

    // 出力
    Console.WriteLine( "StringValue \t= {0}", stringValue );
    Console.WriteLine( "IntValue \t= {0}", intValue );
    Console.WriteLine( "ArrayValue \t= {0},{1},{2}", arrayValue[0], arrayValue[1], arrayValue[2] );

    // 結果：
    // StringValue     = ABCDEFG
    // IntValue        = 12345
    // ArrayValue      = A,B,C
}
```

拡張メソッドをジェネリックにしていますが、「毎回毎回、型を指定するのがめんどう！」「そんなに汎用性を求めてない！」という方は、必要に応じてカスタム属性と拡張メソッドをstring型やint型で型を限定して作って置くのもいいかもしれません。

その際はこんな感じなると思います。

```csharp:Ready.cs
using System;
using System.Linq;
using System.Reflection;

// カスタム属性
[AttributeUsage( AttributeTargets.Field )]
public class StringAttribute : Attribute
{
    public StringAttribute( string value )
    {
        this.String = value;
    }

    public string String { get; private set; }
}

// 拡張メソッド
public static class EnumEx
{
    public static string ToStringFromSubValue( this Enum enm )
    {
        return enm.GetType()
           .GetRuntimeField( enm.ToString() )
           .GetCustomAttributes( typeof( StringAttribute ), false )
           .Cast<StringAttribute>()
           .Single()
           .String;
    }
}
```

そうすれば、多少なりコーディングが楽なるかも？

```csharp:TrySample.cs
enum Example
{
    [StringValue( "ABCDEFG" )]
    StringValue = 0,
}

static void Main()
{
    // こんな感じで取れます
    //string   stringValue   = Example.StringValue.ToSubValue<string>();
    string   stringValue     = Example.StringValue.ToStringFromSubValue();
    
    // 出力
    Console.WriteLine( "StringValue \t= {0}", stringValue );
     
    // 結果：
    // StringValue     = ABCDEFG
}
```

もっとスッキリさせたいが、力量不足なのでこの辺で妥協してます。