enumに情報(属性)を追加して横着する方法


 ゲームとか(に限らないけど)で複数の画像を扱うときに、enumで定数を指定して配列やリストに格納することが多いと思います。


● 例1


【定数定義部】
// 画像ID
enum IMAGE
{
  PLAYER = 1,
  SHOT,
  ENEMY1,
  ENEMY2
}


【画像読み込み部】
// 画像格納コレクション
Dictionary<IMAGE, Bitmap> images = new Dictionary<IMAGE,Bitmap>();
// 画像読み込みと格納
images[IMAGE.PLAYER] = new Bitmap("SpaceShip.png");
images[IMAGE.SHOT] = new Bitmap("Bullet01.png");
images[IMAGE.ENEMY1] = new Bitmap("UFO.png");
images[IMAGE.ENEMY2] = new Bitmap("asteroid.png");



 普通こんな感じで書きますね。
 【定数定義部】で定数を定義して、【画像読み込み部】では読み込んだファイルを定数をキーにしてコレクションに格納。
 ここで問題になるのは画像を1つ追加するにも【定数定義部】と【画像読み込み部】2箇所に書かないといけないということです。
 1対1で対応するはずですが、抜けがあったりしてもすぐにわかりませんし、第一書くときソースを行き来しないといけないので面倒くさい。



 そこで、C#のリフレクションとカスタムアトリビュートを活用してみることにしました。
 リフレクションを使うと、クラスや列挙型などの情報(メンバー名とかメンバー型とか)を詳細に取得することができます。
 そして、カスタムアトリビュートを使うと、クラスやメンバーに独自の属性を追加することができます。



 これを利用して、画像IDの列挙型IMAGEの各要素に、ファイル名属性を追加してみました。



● 例2


【カスタムアトリビュート定義】
// カスタムアトリビュート (ファイル名属性)
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public class ImgInfoAttribute : Attribute
{
  public String filename;
  public ImgInfoAttribute(String str) { this.filename = str; }
}


【定数定義部】
// 画像ID
enum IMAGE
{
  [ImgInfo("SpaceShip.png")]  PLAYER = 1,
  [ImgInfo("Bullet01.png")]   SHOT,
  [ImgInfo("UFO.png")]     ENEMY1,
  [ImgInfo("asteroid.jpg")]   ENEMY2
}


【画像読み込み部】
// 画像格納コレクション
Dictionary<IMAGE, Bitmap> images = new Dictionary<IMAGE,Bitmap>();


// 画像読み込みと格納
foreach (IMAGE enumImage in Enum.GetValues( typeof( IMAGE)) )
{
   ImgInfoAttribute infos = (ImgInfoAttribute)enumImage.GetType().GetField(enumImage.ToString()).GetCustomAttributes(typeof(ImgInfoAttribute), false);
  if (infos.Length > 0)
  {
    images[enumImage] = new Bitmap(infos[0].filename);
  }
}


 【カスタムアトリビュート定義】では、独自属性としてファイル名を扱うクラスを定義しています。
 メンバー変数としてfilenameを持っており、コンストラクタで設定します。
 クラス名は「ImgInfoAttribute」ですが使用する際は「Attribute」を省略して「ImgInfo」として指定します。


 【定数定義部】が、今回のポイントです。
 定数名の前に、カスタムアトリビュート(ImgInfo)でファイル名を指定しています。


 【画像読み込み部】は、リフレクションを使って画像ID列挙型の各メンバーの情報を取得しています。
 処理の流れは以下のとおり
 ・メンバーを列挙
 foreach (IMAGE enumImage in Enum.GetValues( typeof( IMAGE)) ) {}
 ・メンバー名取得
 enumImage.ToString()
 ・カスタムアトリビュート取得
 ImgInfoAttribute infos = (ImgInfoAttribute)enumImage.GetType().GetField(enumImage.ToString()).GetCustomAttributes(typeof(ImgInfoAttribute), false);
 ・カスタムアトリビュートからファイル名取得
 infos[0].filename
 ・ファイルを読み込んでコレクションに登録
 images[enumImage] = new Bitmap(infos[0].filename);


/

 【カスタムアトリビュート定義】や【画像読み込み部】のコードが複雑になりましたが、ここは一度書いてしまえばあとは変更の必要がありません。
 ゲームエンジンなどのライブラリの奥に隠してしまいましょう。
 通常は【定数定義部】部分だけを編集すればいいわけです。楽チンすぎる!


 例1にくらべ、編集箇所が減った上に、定数を2度書く必要がなくなりタイプ量も減ります。
 ※「[ImgInfo("〜")]」の部分はコピペするかスニペットに登録すれべよい


 これでまた安楽プログラミングに一歩近づいた!



/

 enumにカスタムアトリビュートを設定するにあたり、以下のブログ記事が大変参考になりました。ありがとうございます。
 いげ太のブログ: [C#] enum に文字列の属性を


 カスタムアトリビュートは1つの対象に複数登録したり、メンバーを増やしで扱う情報を増やしたりとまだまだ機能がいっぱいなので、もっと研究していこうと思います。