2013/11/07

C# Tips1 foreachの活用

リフレッシュスペース(部室)が使えないので、駄文を書いていきます。1年生の参考になるといいな。
コンピュータなんてものは詰まるところ、条件分岐とgotoの塊なんです。決められたとおりにしか動きませんし、決められたことなら何でもできます。プログラミング言語ではif, while(for)が使えればつまるところ何でもできますが、それはとても見づらいプログラムなんですね。
例えばコントローラーを操作するプログラムを考えましょう。 コントローラーの値を取得するために、for文で取得するとします。


for(int i = 0; i < controller_number; i++) {
   state = controller[i].getState();
   for(int j = 0; j < state.button_number; j++) {
      if((state.button[j] & 0x80) == 0x80) {
         controller[i].inputButton(j);
      }
   }
}
controllerはControllerクラスの配列で、そのサイズがcontroller_numberで取得できているとします。 続いて、stateはcontrollerのメソッドgetStateで取得した値で、ボタンのON/OFFやアナログスティックの値を記録しています。これはDirectInputを想定しています。 ボタンが押されたら、0x80でマスクしてそれが0x80と一致するかどうかチェックして、一致していたらコントローラーのボタンメソッドを呼び出します。
もちろんこれはこれでいいのですが、このようなコードは冗長だと思いませんか。 毎回毎回おきまりのint i = 0してi++とおきまりのコードを書く。 ぱっと見、このループが特別な処理のためのループなのか、ある配列などを反復して処理しているだけなのか分かりません。
もしもC#のforeachを使って次のように書けたら幸せでしょう。
foreach(Controller controller in controllers) { // iがない
 foreach(State.Button state_button in controller.getState().getButton()) {
  // このState.Buttonは実質byteである
  if((state_button & 0x80) == 0x80) {
   controller.inputButton(j);
  }
 }
}
iとかjとかあるコードよりもぱっと見て反復していることが分かりよいでしょう。 また、iとかjとかに悩まされることもありません。
それでは自分で実装してみましょう。foreachを使うには、foreach用に System.Collections.IEnumerableインターフェースを実装する必要があります。 IEnumerableは public System.Collections.IEnumerator GetEnumerator() を実装すれば良いです。これはforeach(★ in ●)の(●)にあたるオブジェクトのIFです。 IEnumeratorは状態をリセットするpublic void Reset()と、 次へ進むpublic bool MoveNext()。ただし繰り返すときだけtrueを返す。 そして現在の状態(★)を返すobject System.Collections.IEnumerator.Currentプロパティが必要です。
次の例ではMyClassにあるプロパティary (int型配列)をクラスそのものを使ってforeachで取得できるようにしたものです。
using System.Timers;

class Program {
   public static void Main() {
      MyClass myclass = new MyClass();

      myclass.setSeriesData();

      foreach(int x in myclass) {
         System.Console.WriteLine("{0}", x);
      }
   }
}

class MyClass : System.Collections.IEnumerable {
   int[] ary;
   const int MAX_SIZE = 32;

   public MyClass() {
      ary = new int[MAX_SIZE];
   }

   public void setSeriesData(){
      for(int i = 0; i < MAX_SIZE; i++) {
         ary[i] = i;
      }
   }

   public System.Collections.IEnumerator GetEnumerator() {
      return new MyClassEnum(this);
   }

   class MyClassEnum : System.Collections.IEnumerator {
      int current;
      MyClass myclass;

      public MyClassEnum(MyClass myclass) {
         this.myclass = myclass;
      }

      object System.Collections.IEnumerator.Current {
         get {
            return myclass.ary[current];
         }
      }

      public void Reset() {
         current = 0;
      }

      public bool MoveNext() {
         current++;
         return current < MyClass.MAX_SIZE;
      }
   }
}

0 件のコメント:

コメントを投稿