連載

C#入門

第15 インターフェイスの活用

(株)ピーデー
川俣 晶
2001/11/10


同名のメソッドを持つインターフェイス

 あるクラスで2つのインターフェイスを実装しようとしたが、偶然にも同じ名前のメソッドが定義されていたらどうなるだろうか。以下は、そのような例を実際に記述してみた例である。

   1: using System;
   2:
   3: namespace ConsoleApplication98
   4: {
   5:   interface ISample1
   6:   {
   7:     void task();
   8:   }
   9:   interface ISample2
  10:   {
  11:     void task();
  12:   }
  13:   class Class2 : ISample1
  14:   {
  15:     public void task()
  16:     {
  17:       Console.WriteLine("task() in class2 called");
  18:     }
  19:   }
  20:   class Class3 : ISample1, ISample2
  21:   {
  22:     void ISample1.task()
  23:     {
  24:       Console.WriteLine("ISample1.task() in class3 called");
  25:     }
  26:     void ISample2.task()
  27:     {
  28:       Console.WriteLine("ISample2.task() in class3 called");
  29:     }
  30:   }
  31:   class Class1
  32:   {
  33:     static void Main(string[] args)
  34:     {
  35:       Class2 c2 = new Class2();
  36:       c2.task();
  37:       Class3 c3 = new Class3();
  38:       //c3.task();  // 'ConsoleApplication98.Class3' に 'task' の定義がありません。
  39:       ISample1 i1 = (ISample1)c3;
  40:       i1.task();
  41:       ISample2 i2 = (ISample2)c3;
  42:       i2.task();
  43:     }
  44:   }
  45: }
同名のメソッドを含む2つのインターフェイスを実装したサンプル・プログラム6
Class3(20行目)では、メソッドの定義をISample1.task(22行目)やISample2.task(26行目)のように、実装するインターフェイスを明示して記述する必要がある。

 これを実行すると以下のようになる。

サンプル・プログラム6の実行結果
オブジェクトをインターフェイスにキャストしてからメソッドを呼び出す必要がある。

 5〜8行目のインターフェイスISample1と、9〜12行目のインターフェイスISample2は、どちらも同じtaskというメソッドを持っている。この2つのインターフェイスを20行目のように1つのクラスで実装しようとすると、戻り値も引数も共通なので、そのままでは区別することができない。そこで、どのインターフェイス由来のメソッドを実装しようとしているのか、インターフェイス名を明示して記述する。22〜25行目はtaskではなく、ISample1.taskと明示的に書くことで、インターフェイスISample1のtaskを実装していることを明示している。同じように、26〜29行目でも同じようにインターフェイスISample2のtaskを実装している。

 さて、呼び出す方もひと工夫が必要である。同じ名前のメソッドが相手なので、36行目のような単純な呼び出し方法ではうまく行かないのである。そこで、39〜42行目を見ていただきたい。ここにあるように、まずインターフェイスにキャストしてから呼び出せば、曖昧さはなくなるのである。例えば、39行目では、クラスClass3のインスタンスを、インターフェイスISample1にキャストしている。キャスト結果に対して40行目ではtaskメソッドを呼び出しているが、このコードは間違いなく、インターフェイスISample1のtaskメソッドを呼び出す。同様に、41〜42行目では、間違いなく、インターフェイスISample2のtaskメソッドを呼び出す。

自作クラスをforeach可能にする

 さて、ここからはお待ちかねの活用編だ。システムで定義されたインターフェイスを自作クラスに実装すると何ができるか見ていこう。まず最初は、自作クラスをforeach可能にする方法からだ。

 C#のforeach文は、Visual Basicなどから取り入れた構文と思われるが、配列などを渡すだけで、自動的にすべての要素を繰り返してくれる便利なものである。これは、.NET Frameworkに含まれる多くのクラスで利用できるが、なぜこれほど多くのクラスで使えるかというと、繰り返しのメカニズムを、インターフェイスを経由して実現しているからだ。つまり、インターフェイスさえ実装されていれば、どんなクラスであろうと、foreachで利用できるのである。もちろん、自作クラスでも問題ない。

 以下は実際に自作クラスをforeach可能にした例である。

   1: using System;
   2: using System.Collections;
   3:
   4: namespace ConsoleApplication99
   5: {
   6:   class ClassMyEnumerator : IEnumerator
   7:   {
   8:     private int pointer;
   9:     string [] target;
  10:     object IEnumerator.Current
  11:     {
  12:       get { return target[pointer]; }
  13:     }
  14:     bool IEnumerator.MoveNext()
  15:     {
  16:       if( pointer >= target.GetUpperBound(0) ) return false;
  17:       pointer++;
  18:       return true;
  19:     }
  20:     void IEnumerator.Reset()
  21:     {
  22:       pointer = target.GetLowerBound(0)-1;
  23:     }
  24:     public ClassMyEnumerator( string [] array )
  25:     {
  26:       target = array;
  27:       pointer = target.GetLowerBound(0)-1;
  28:     }
  29:   }
  30:   class ClassSample : IEnumerable
  31:   {
  32:     string [] ar = { "ABC", "DEF", "GHI" };
  33:     IEnumerator IEnumerable.GetEnumerator()
  34:     {
  35:       return new ClassMyEnumerator(ar);
  36:     }
  37:   }
  38:   class Class1
  39:   {
  40:     static void Main(string[] args)
  41:     {
  42:       ClassSample c = new ClassSample();
  43:       foreach( string s in c )
  44:       {
  45:         Console.WriteLine(s);
  46:       }
  47:     }
  48:   }
  49: }
自作クラスをforeach可能にしたサンプル・プログラム7
IEnumerableとIEnumeratorの2つのインターフェイスを使用する。

 これを実行すると以下のようになる。

サンプル・プログラム7の実行結果
クラス内であらかじめ定義されている文字列を、foreach文を使用して順に取り出すことができる。

 ここでは2つのインターフェイスが登場している。1つ目は、IEnumerableで、これを実装したクラスは、foreach可能と見なされる。2つ目はIEnumeratorで、実際に要素を1個ずつ列挙する機能を提供する。IEnumerableは、あなたの自作クラスに実装し、IEnumeratorはあなたの自作クラスを列挙するクラスに実装するものである。

 では、30〜37行目を見ていただきたい。ここでは、3つの文字列を内部に持つクラスが定義されている。実際には32行目の配列はそのままforeach可能なので、いちいちインターフェイスを実装しなくてもよいのだが、ここではサンプル・プログラムということで、あえて実装してみよう。foreach可能にするには、IEnumerableを実装するのだが、これには、GetEnumeratorというメソッドだけが宣言されている。そこで、33〜36行目で、そのメソッドを実装している。といっても、込み入ったことは何もしていない。やるべきことは、列挙を実際に行うクラスのインスタンスを作成して、それを返してやるだけだ。

 実際に列挙するのは、ちょっと長いが、6〜29行目のClassMyEnumeratorクラスだ。IEnumeratorに含まれるCurrentプロパティ、MoveNextメソッド、Resetメソッドを実装すればよい。このほかに、初期化のために24〜28行目にコンストラクタも書いている。まず、20〜23行目のResetメソッドは、列挙の初期化を行う。列挙は、最初のアイテムの1個手前の状態から始まるので、22行目の最後でマイナス1をしている。次は14〜19行目のMoveNextメソッドである。このメソッドは、次の要素に1個進める機能を持つ。17行目のようにアクセスする対象を示す値をプラス1するのが基本動作だ。そして、いよいよデータそのものにアクセスするのが、Currentプロパティだ。これは読み出し専用で、MoveNextメソッドで進めた場所にある内容を返す。なお、GetUpperBoundメソッドは配列の上限の添え字を返すメソッド、GetLowerBoundは同様に下限を返すメソッドである。

 以上のような処理が記述されていれば、42〜46行目のように、自作クラスのインスタンスを、いきなりforeach文で使うことができる。


 INDEX
  第15回 インターフェイスの活用
    1.インターフェイスの効能
    2.インターフェイスの継承
  3.同名のメソッドを持つインターフェイス
    4.ソート順を操る

「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 記事ランキング

本日 月間