連載:[完全版]究極のC#プログラミング

Chapter8 部分クラスと静的クラス

川俣 晶
2009/11/16

8.3 リフレクションと部分クラス

 部分クラスの効能として、8.1節では、「巨大なクラスを複数のソースファイルに分割して記述できる」と述べたが、これには異論がありうるだろう。1つのソースファイルに収まらないような巨大なクラスを作成すると保守性が下がるので、作るべきではない……というのは1つの健全な発想だ。

 しかし、C# 3.0の世界では別の可能性が存在するのではないかと考える。

 第1章「1.5 後退するクラスの立場」でも述べたように、C# 3.0では動作をカスタマイズする主体を、クラスではなくメソッドにすることができる。そのような方針でコードを記述すると、クラスの役割はメソッドやデータの入れ物へと後退する。

 単なる入れ物へと立場を後退させたクラスは、データもメソッドも格納できる一種のコレクションのような存在になる。そして、格納されたデータやメソッドは、リフレクションの機能を用いて容易に列挙して扱うことができる。

 さて、クラスを単なる入れ物と割り切って使うとすれば、それに格納されるアイテムの数はさまざまである。少数であることもあれば、多数であることもあるだろう。それは自然な成り行きであって、多くを格納することは間違いとはいえない。そして、なにより、リフレクションによる列挙という使われ方を前提とするなら、格納されるアイテムがどれほど増えようとも、別のクラスに分割することはできない。あるカテゴリーに属するデータやメソッドは特定のクラスに含まれているという前提での列挙になるためだ。

 そのような記述方法は机上の空論ではなく、実際に筆者が同人ソフトを書く際に使ってうまく機能した実績がある(このようなリスキーなチャレンジに、同人ソフトはもってこいである。具体的な顧客に依頼されて書くコードで、このような冒険はできない)。

 実際に、その同人ソフトのコードの一部を抜粋して、リスト8.4リスト8.5として紹介しよう。

public static class AdviceInfos
{
  [AdviceInfo(50, "必読最初のアドバイス")]
  public static readonly AdviceInfo 必読最初のアドバイス = new AdviceInfo(
    new string[] {
      "まず私から出されるアドバイスはすべて読むとよいと思います。",
       …
    },
    ()=>
    {
      return true;
    });

  [AdviceInfo(200,"努町での買い物")]
  public static readonly AdviceInfo 努町での買い物 = new AdviceInfo(
    new string[] {
      "駅の近くに商店街があります。",
       …
    },
    ()=>
    {
      General.LearnPlace(Places.Place努町商店街);
      return true;
    });
   …
}
リスト8.4 クラスを入れ物として使った例

public class AdviseInfoList
{
  public static AdviceInfo[] GetList()
  {
    return list.ToArray();
  }

  private static List<AdviceInfo> list = new List<AdviceInfo>();

  static AdviseInfoList()
  {
    List<int> idList = new List<int>();

    foreach (System.Reflection.FieldInfo info
        in typeof(AdviceInfos).GetFields())
    {
      object[] result = info.GetCustomAttributes(
                          typeof(AdviceInfoAttribute), false);

      if (result.Length != 0)
      {
        int id = ((AdviceInfoAttribute)result[0]).ID;

        if (idList.Contains(id))
        {
          throw new ApplicationException(
            "AdviceInfo ID " + id.ToString()
            + " は重複しています。");
        }
        idList.Add(id);
        AdviceInfo advice = (AdviceInfo)info.GetValue(null);
        advice.ID = id;
        advice.Name = ((AdviceInfoAttribute)result[0]).Name;
        list.Add(advice);
      }
    }

    // 本当に必要かわからないが、ID順ソートしておく
    list.Sort((x, y)=>
    {
      return x.ID - y.ID;
    });
  }
}
リスト8.5 リスト8.4のクラスから一覧表を作成するクラス

 リスト8.4のAdviceInfosクラスは、ゲーム中で行われる「アドバイス」の情報をまとめて入れておく「入れ物」として機能している。しかし、このクラスは、表示されるメッセージ本体を含むために、長くなりがちである。実際、このクラスは646行あった。かろうじて1ファイルでも扱えるサイズだが、2倍になれば分割を考えたいところだ。その場合は、当然、部分クラスの機能を使用できることになる。

 さて、このようなコードを見て、不自然な書き方だと思った読者も多いと思う。単に多数のオブジェクトを格納しておくだけなら、配列でもよいはずである。だが、配列ではこのコードほどうまく扱うことができない。その理由は次のとおりである。

  • 配列の初期化構文はクラスと違って複数ファイルに分割できない
  • 特定の1つのアドバイスを参照する際、リスト8.4のコードでは「AdviceInfos.必読最初のアドバイス」と記述できるが、配列を使うと「AdviceInfos[0]」のような表記になり、わかりにくい「0」というマジックナンバーが入り込んでしまう

 後者の問題を解消するために、0を定数に定義しても、定義された定数と実際の内容が食い違ってしまうリスクを排除できない。また、配列ではなくDictionaryクラスのようなコレクションを使うと、「AdviceInfos["必読最初のアドバイス"]」のように記述することもできる。これなら、マジックナンバーを覚えずに済むが、本当に存在するアドバイスであるか否かが、コンパイル時にはチェックできない。

 上記のクラスを入れ物として使う方式は、これらの問題をすべて解決している。

 このような書き方が価値を持つことは、少なくとも私の書いているいくつかのプログラムでは証明済みである。あらゆるプログラムで有効であるかまではわからないが、仮にこのような書き方が“あり”だとすれば、部分クラスの機能によってクラスを複数ファイルに分割できることは、大きな助けになるだろう。


 INDEX
  [完全版]究極のC#プログラミング
  Chapter8 部分クラスと静的クラス
    1.8.1 部分クラス(Partial Class)
    2.8.2 自動生成コードと安全に共存する
  3.8.3 リフレクションと部分クラス
    4.8.4 部分クラスを使ううえでの注意点
    5.8.5 静的クラス(Static Class)/【Exercise】練習問題
 
インデックス・ページヘ  「[完全版]究極のC#プログラミング」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間